diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 6592c9ee4c1a..bd0c28971a1b 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.3 + +* Support Android V2 embedding. +* Migrate to using the new e2e test binding. + ## 0.6.2+3 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index e34a3b5632c0..f2a0b02c821d 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -199,6 +199,7 @@ public void onScanCompleted(String path, Uri uri) { this.cache = cache; } + // Save the state of the image picker so it can be retrieved with `retrieveLostImage`. void saveStateBeforeResult() { if (methodCall == null) { return; diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index b495a8e1a33f..950304c9c9e7 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -10,13 +10,86 @@ import android.os.Environment; import android.os.Handler; import android.os.Looper; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +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.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; import java.io.File; -public class ImagePickerPlugin implements MethodChannel.MethodCallHandler { +@SuppressWarnings("deprecation") +public class ImagePickerPlugin + implements MethodChannel.MethodCallHandler, FlutterPlugin, ActivityAware { + + private class LifeCycleObserver + implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { + private final Activity thisActivity; + + LifeCycleObserver(Activity activity) { + this.thisActivity = activity; + } + + @Override + public void onCreate(@NonNull LifecycleOwner owner) {} + + @Override + public void onStart(@NonNull LifecycleOwner owner) {} + + @Override + public void onResume(@NonNull LifecycleOwner owner) {} + + @Override + public void onPause(@NonNull LifecycleOwner owner) {} + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + onActivityStopped(thisActivity); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + onActivityDestroyed(thisActivity); + } + + @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 (thisActivity == activity && activity.getApplicationContext() != null) { + ((Application) activity.getApplicationContext()) + .unregisterActivityLifecycleCallbacks( + this); // Use getApplicationContext() to avoid casting failures + } + } + + @Override + public void onActivityStopped(Activity activity) { + if (thisActivity == activity) { + delegate.saveStateBeforeResult(); + } + } + } static final String METHOD_CALL_IMAGE = "pickImage"; static final String METHOD_CALL_VIDEO = "pickVideo"; @@ -27,9 +100,15 @@ public class ImagePickerPlugin implements MethodChannel.MethodCallHandler { private static final int SOURCE_CAMERA = 0; private static final int SOURCE_GALLERY = 1; - private final PluginRegistry.Registrar registrar; + private MethodChannel channel; private ImagePickerDelegate delegate; - private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks; + private FlutterPluginBinding pluginBinding; + private ActivityPluginBinding activityBinding; + private Application application; + private Activity activity; + // This is null when not using v2 embedding; + private Lifecycle lifecycle; + private LifeCycleObserver observer; public static void registerWith(PluginRegistry.Registrar registrar) { if (registrar.activity() == null) { @@ -37,72 +116,114 @@ public static void registerWith(PluginRegistry.Registrar registrar) { // we stop the registering process immediately because the ImagePicker requires an activity. return; } - final ImagePickerCache cache = new ImagePickerCache(registrar.activity()); + Activity activity = registrar.activity(); + Application application = null; + if (registrar.context() != null) { + application = (Application) (registrar.context().getApplicationContext()); + } + ImagePickerPlugin plugin = new ImagePickerPlugin(); + plugin.setup(registrar.messenger(), application, activity, registrar, null); + } - final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL); + /** + * Default constructor for the plugin. + * + *
Use this constructor for production code.
+ */
+ // See also: * {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
+ public ImagePickerPlugin() {}
- final File externalFilesDirectory =
- registrar.activity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
- final ExifDataCopier exifDataCopier = new ExifDataCopier();
- final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
- final ImagePickerDelegate delegate =
- new ImagePickerDelegate(registrar.activity(), externalFilesDirectory, imageResizer, cache);
+ @VisibleForTesting
+ ImagePickerPlugin(final ImagePickerDelegate delegate, final Activity activity) {
+ this.delegate = delegate;
+ this.activity = activity;
+ }
- registrar.addActivityResultListener(delegate);
- registrar.addRequestPermissionsResultListener(delegate);
- final ImagePickerPlugin instance = new ImagePickerPlugin(registrar, delegate);
- channel.setMethodCallHandler(instance);
+ @Override
+ public void onAttachedToEngine(FlutterPluginBinding binding) {
+ pluginBinding = binding;
}
- @VisibleForTesting
- ImagePickerPlugin(final PluginRegistry.Registrar registrar, final ImagePickerDelegate delegate) {
- this.registrar = registrar;
- this.delegate = delegate;
- this.activityLifecycleCallbacks =
- new Application.ActivityLifecycleCallbacks() {
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
+ @Override
+ public void onDetachedFromEngine(FlutterPluginBinding binding) {
+ pluginBinding = null;
+ }
- @Override
- public void onActivityStarted(Activity activity) {}
+ @Override
+ public void onAttachedToActivity(ActivityPluginBinding binding) {
+ activityBinding = binding;
+ setup(
+ pluginBinding.getBinaryMessenger(),
+ (Application) pluginBinding.getApplicationContext(),
+ activityBinding.getActivity(),
+ null,
+ activityBinding);
+ }
- @Override
- public void onActivityResumed(Activity activity) {}
+ @Override
+ public void onDetachedFromActivity() {
+ tearDown();
+ }
- @Override
- public void onActivityPaused(Activity activity) {}
+ @Override
+ public void onDetachedFromActivityForConfigChanges() {
+ onDetachedFromActivity();
+ }
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
- if (activity == registrar.activity()) {
- delegate.saveStateBeforeResult();
- }
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- if (activity == registrar.activity()
- && registrar.activity().getApplicationContext() != null) {
- ((Application) registrar.activity().getApplicationContext())
- .unregisterActivityLifecycleCallbacks(
- this); // Use getApplicationContext() to avoid casting failures
- }
- }
-
- @Override
- public void onActivityStopped(Activity activity) {}
- };
-
- if (this.registrar != null
- && this.registrar.context() != null
- && this.registrar.context().getApplicationContext() != null) {
- ((Application) this.registrar.context().getApplicationContext())
- .registerActivityLifecycleCallbacks(
- this
- .activityLifecycleCallbacks); // Use getApplicationContext() to avoid casting failures.
+ @Override
+ public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
+ onAttachedToActivity(binding);
+ }
+
+ private void setup(
+ final BinaryMessenger messenger,
+ final Application application,
+ final Activity activity,
+ final PluginRegistry.Registrar registrar,
+ final ActivityPluginBinding activityBinding) {
+ this.activity = activity;
+ this.application = application;
+ this.delegate = constructDelegate(activity);
+ channel = new MethodChannel(messenger, CHANNEL);
+ channel.setMethodCallHandler(this);
+ observer = new LifeCycleObserver(activity);
+ if (registrar != null) {
+ // V1 embedding setup for activity listeners.
+ application.registerActivityLifecycleCallbacks(observer);
+ registrar.addActivityResultListener(delegate);
+ registrar.addRequestPermissionsResultListener(delegate);
+ } else {
+ // V2 embedding setup for activity listeners.
+ activityBinding.addActivityResultListener(delegate);
+ activityBinding.addRequestPermissionsResultListener(delegate);
+ lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding);
+ lifecycle.addObserver(observer);
}
}
+ private void tearDown() {
+ activityBinding.removeActivityResultListener(delegate);
+ activityBinding.removeRequestPermissionsResultListener(delegate);
+ activityBinding = null;
+ lifecycle.removeObserver(observer);
+ lifecycle = null;
+ delegate = null;
+ channel.setMethodCallHandler(null);
+ channel = null;
+ application.unregisterActivityLifecycleCallbacks(observer);
+ application = null;
+ }
+
+ private final ImagePickerDelegate constructDelegate(final Activity setupActivity) {
+ final ImagePickerCache cache = new ImagePickerCache(setupActivity);
+
+ final File externalFilesDirectory =
+ setupActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ final ExifDataCopier exifDataCopier = new ExifDataCopier();
+ final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
+ return new ImagePickerDelegate(setupActivity, externalFilesDirectory, imageResizer, cache);
+ }
+
// MethodChannel.Result wrapper that responds on the platform thread.
private static class MethodResultWrapper implements MethodChannel.Result {
private MethodChannel.Result methodResult;
@@ -150,7 +271,7 @@ public void run() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) {
- if (registrar.activity() == null) {
+ if (activity == null) {
rawResult.error("no_activity", "image_picker plugin requires a foreground activity.", null);
return;
}
diff --git a/packages/image_picker/example/android/app/src/main/AndroidManifest.xml b/packages/image_picker/example/android/app/src/main/AndroidManifest.xml
index fa2b500d6904..5de9f044dd84 100755
--- a/packages/image_picker/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/image_picker/example/android/app/src/main/AndroidManifest.xml
@@ -4,8 +4,7 @@