[Share] Support v2 android embedder. (#2156)
diff --git a/packages/share/CHANGELOG.md b/packages/share/CHANGELOG.md index 087c66b..fa8b261 100644 --- a/packages/share/CHANGELOG.md +++ b/packages/share/CHANGELOG.md
@@ -1,3 +1,10 @@ +## 0.6.3 + +* Support the v2 Android embedder. +* Update to AndroidX. +* Migrate to using the new e2e test binding. +* Add a e2e test. + ## 0.6.2+4 * Define clang module for iOS.
diff --git a/packages/share/android/build.gradle b/packages/share/android/build.gradle index 3b265e6..1461873 100644 --- a/packages/share/android/build.gradle +++ b/packages/share/android/build.gradle
@@ -45,3 +45,29 @@ disable 'InvalidPackage' } } + +// TODO(cyanglaz): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + api "android.arch.lifecycle:runtime:$lifecycle_version" + api "android.arch.lifecycle:common:$lifecycle_version" + api "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +}
diff --git a/packages/share/android/src/main/java/io/flutter/plugins/share/MethodCallHandler.java b/packages/share/android/src/main/java/io/flutter/plugins/share/MethodCallHandler.java new file mode 100644 index 0000000..f7e4d57 --- /dev/null +++ b/packages/share/android/src/main/java/io/flutter/plugins/share/MethodCallHandler.java
@@ -0,0 +1,33 @@ +// Copyright 2019 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.share; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.Map; + +/** Handles the method calls for the plugin. */ +class MethodCallHandler implements MethodChannel.MethodCallHandler { + + private Share share; + + MethodCallHandler(Share share) { + this.share = share; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + if (call.method.equals("share")) { + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + // Android does not support showing the share sheet at a particular point on screen. + share.share((String) call.argument("text"), (String) call.argument("subject")); + result.success(null); + } else { + result.notImplemented(); + } + } +}
diff --git a/packages/share/android/src/main/java/io/flutter/plugins/share/Share.java b/packages/share/android/src/main/java/io/flutter/plugins/share/Share.java new file mode 100644 index 0000000..8c9e833 --- /dev/null +++ b/packages/share/android/src/main/java/io/flutter/plugins/share/Share.java
@@ -0,0 +1,50 @@ +// Copyright 2019 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.share; + +import android.app.Activity; +import android.content.Intent; + +/** Handles share intent. */ +class Share { + + private Activity activity; + + /** + * Constructs a Share object. The {@code activity} is used to start the share intent. It might be + * null when constructing the {@link Share} object and set to non-null when an activity is + * available using {@link #setActivity(Activity)}. + */ + Share(Activity activity) { + this.activity = activity; + } + + /** + * Sets the activity when an activity is available. When the activity becomes unavailable, use + * this method to set it to null. + */ + void setActivity(Activity activity) { + this.activity = activity; + } + + void share(String text, String subject) { + if (text == null || text.isEmpty()) { + throw new IllegalArgumentException("Non-empty text expected"); + } + + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, text); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + shareIntent.setType("text/plain"); + Intent chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */); + if (activity != null) { + activity.startActivity(chooserIntent); + } else { + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.startActivity(chooserIntent); + } + } +}
diff --git a/packages/share/android/src/main/java/io/flutter/plugins/share/SharePlugin.java b/packages/share/android/src/main/java/io/flutter/plugins/share/SharePlugin.java index 60b83e4..443f28e 100644 --- a/packages/share/android/src/main/java/io/flutter/plugins/share/SharePlugin.java +++ b/packages/share/android/src/main/java/io/flutter/plugins/share/SharePlugin.java
@@ -4,59 +4,68 @@ package io.flutter.plugins.share; -import android.content.Intent; -import io.flutter.plugin.common.MethodCall; +import android.app.Activity; +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; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.Map; /** Plugin method host for presenting a share sheet via Intent */ -public class SharePlugin implements MethodChannel.MethodCallHandler { +public class SharePlugin implements FlutterPlugin, ActivityAware { private static final String CHANNEL = "plugins.flutter.io/share"; + private MethodCallHandler handler; + private Share share; + private MethodChannel methodChannel; public static void registerWith(Registrar registrar) { - MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL); - SharePlugin instance = new SharePlugin(registrar); - channel.setMethodCallHandler(instance); - } - - private final Registrar mRegistrar; - - private SharePlugin(Registrar registrar) { - this.mRegistrar = registrar; + SharePlugin plugin = new SharePlugin(); + plugin.setUpChannel(registrar.activity(), registrar.messenger()); } @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { - if (call.method.equals("share")) { - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - // Android does not support showing the share sheet at a particular point on screen. - share((String) call.argument("text"), (String) call.argument("subject")); - result.success(null); - } else { - result.notImplemented(); - } + public void onAttachedToEngine(FlutterPluginBinding binding) { + setUpChannel(null, binding.getFlutterEngine().getDartExecutor()); } - private void share(String text, String subject) { - if (text == null || text.isEmpty()) { - throw new IllegalArgumentException("Non-empty text expected"); - } + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + methodChannel.setMethodCallHandler(null); + methodChannel = null; + share = null; + } - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, text); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); - shareIntent.setType("text/plain"); - Intent chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */); - if (mRegistrar.activity() != null) { - mRegistrar.activity().startActivity(chooserIntent); - } else { - chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mRegistrar.context().startActivity(chooserIntent); - } + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + share.setActivity(binding.getActivity()); + } + + @Override + public void onDetachedFromActivity() { + tearDownChannel(); + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); + } + + private void setUpChannel(Activity activity, BinaryMessenger messenger) { + methodChannel = new MethodChannel(messenger, CHANNEL); + share = new Share(activity); + handler = new MethodCallHandler(share); + methodChannel.setMethodCallHandler(handler); + } + + private void tearDownChannel() { + share.setActivity(null); + methodChannel.setMethodCallHandler(null); } }
diff --git a/packages/share/example/android/app/src/main/AndroidManifest.xml b/packages/share/example/android/app/src/main/AndroidManifest.xml index a86d072..d5e5ec8 100644 --- a/packages/share/example/android/app/src/main/AndroidManifest.xml +++ b/packages/share/example/android/app/src/main/AndroidManifest.xml
@@ -4,12 +4,18 @@ <uses-permission android:name="android.permission.INTERNET"/> <application android:name="io.flutter.app.FlutterApplication" android:label="share_example" android:icon="@mipmap/ic_launcher"> - <activity android:name=".MainActivity" + <activity android:name=".EmbeddingV1Activity" android:launchMode="singleTop" android:theme="@android:style/Theme.Black.NoTitleBar" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + </activity> + <activity android:name=".MainActivity" + android:theme="@android:style/Theme.Black.NoTitleBar" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" + android:hardwareAccelerated="true" + android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1Activity.java b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1Activity.java new file mode 100644 index 0000000..736ac54 --- /dev/null +++ b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1Activity.java
@@ -0,0 +1,18 @@ +// Copyright 2017 The Chromium 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.shareexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +}
diff --git a/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1ActivityTest.java b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1ActivityTest.java new file mode 100644 index 0000000..9585411 --- /dev/null +++ b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/EmbeddingV1ActivityTest.java
@@ -0,0 +1,13 @@ +package io.flutter.plugins.shareexample; + +import androidx.test.rule.ActivityTestRule; +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); +}
diff --git a/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivity.java b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivity.java index 89d8bb2..3717feb 100644 --- a/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivity.java +++ b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivity.java
@@ -1,18 +1,19 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2019 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.shareexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.share.SharePlugin; public class MainActivity extends FlutterActivity { - + // TODO(cyanglaz): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. + // https://github.com/flutter/flutter/issues/42694 @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + flutterEngine.getPlugins().add(new SharePlugin()); } }
diff --git a/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivityTest.java b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivityTest.java new file mode 100644 index 0000000..fcd936a --- /dev/null +++ b/packages/share/example/android/app/src/main/java/io/flutter/plugins/shareexample/MainActivityTest.java
@@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium 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.shareexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class); +}
diff --git a/packages/share/example/android/gradle.properties b/packages/share/example/android/gradle.properties index 8bd86f6..38c8d45 100644 --- a/packages/share/example/android/gradle.properties +++ b/packages/share/example/android/gradle.properties
@@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true
diff --git a/packages/share/example/pubspec.yaml b/packages/share/example/pubspec.yaml index f28c6dc..4e4fc49 100644 --- a/packages/share/example/pubspec.yaml +++ b/packages/share/example/pubspec.yaml
@@ -7,5 +7,15 @@ share: path: ../ +dev_dependencies: + flutter_driver: + sdk: flutter + e2e: ^0.2.0 + flutter: uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.2 <2.0.0" +
diff --git a/packages/share/example/test_driver/test/share_e2e_test.dart b/packages/share/example/test_driver/test/share_e2e_test.dart new file mode 100644 index 0000000..ff6e9ce --- /dev/null +++ b/packages/share/example/test_driver/test/share_e2e_test.dart
@@ -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:async'; +import 'dart:io'; +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)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +}
diff --git a/packages/share/pubspec.yaml b/packages/share/pubspec.yaml index 800f1b4..d569cbf 100644 --- a/packages/share/pubspec.yaml +++ b/packages/share/pubspec.yaml
@@ -3,7 +3,7 @@ the ACTION_SEND intent on Android and UIActivityViewController on iOS. author: Flutter Team <flutter-dev@googlegroups.com> homepage: https://github.com/flutter/plugins/tree/master/packages/share -version: 0.6.2+4 +version: 0.6.3 flutter: plugin: @@ -21,7 +21,8 @@ mockito: ^3.0.0 flutter_test: sdk: flutter + e2e: ^0.2.0 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.6.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0"
diff --git a/packages/share/test/share_e2e.dart b/packages/share/test/share_e2e.dart new file mode 100644 index 0000000..eb99022 --- /dev/null +++ b/packages/share/test/share_e2e.dart
@@ -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 'package:flutter_test/flutter_test.dart'; +import 'package:share/share.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Can launch share', (WidgetTester tester) async { + expect(Share.share('message', subject: 'title'), completes); + }); +}