Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 3595743

Browse files
author
Chris Yang
authored
[image_picker] support android V2 embedding (#2430)
1 parent 484e362 commit 3595743

File tree

13 files changed

+268
-88
lines changed

13 files changed

+268
-88
lines changed

packages/image_picker/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.6.3
2+
3+
* Support Android V2 embedding.
4+
* Migrate to using the new e2e test binding.
5+
16
## 0.6.2+3
27
* Remove the deprecated `author:` field from pubspec.yaml
38
* Migrate the plugin to the pubspec platforms manifest.

packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ public void onScanCompleted(String path, Uri uri) {
199199
this.cache = cache;
200200
}
201201

202+
// Save the state of the image picker so it can be retrieved with `retrieveLostImage`.
202203
void saveStateBeforeResult() {
203204
if (methodCall == null) {
204205
return;

packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java

+179-58
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,86 @@
1010
import android.os.Environment;
1111
import android.os.Handler;
1212
import android.os.Looper;
13+
import androidx.annotation.NonNull;
1314
import androidx.annotation.VisibleForTesting;
15+
import androidx.lifecycle.DefaultLifecycleObserver;
16+
import androidx.lifecycle.Lifecycle;
17+
import androidx.lifecycle.LifecycleOwner;
18+
import io.flutter.embedding.engine.plugins.FlutterPlugin;
19+
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
20+
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
21+
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
22+
import io.flutter.plugin.common.BinaryMessenger;
1423
import io.flutter.plugin.common.MethodCall;
1524
import io.flutter.plugin.common.MethodChannel;
1625
import io.flutter.plugin.common.PluginRegistry;
1726
import java.io.File;
1827

19-
public class ImagePickerPlugin implements MethodChannel.MethodCallHandler {
28+
@SuppressWarnings("deprecation")
29+
public class ImagePickerPlugin
30+
implements MethodChannel.MethodCallHandler, FlutterPlugin, ActivityAware {
31+
32+
private class LifeCycleObserver
33+
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
34+
private final Activity thisActivity;
35+
36+
LifeCycleObserver(Activity activity) {
37+
this.thisActivity = activity;
38+
}
39+
40+
@Override
41+
public void onCreate(@NonNull LifecycleOwner owner) {}
42+
43+
@Override
44+
public void onStart(@NonNull LifecycleOwner owner) {}
45+
46+
@Override
47+
public void onResume(@NonNull LifecycleOwner owner) {}
48+
49+
@Override
50+
public void onPause(@NonNull LifecycleOwner owner) {}
51+
52+
@Override
53+
public void onStop(@NonNull LifecycleOwner owner) {
54+
onActivityStopped(thisActivity);
55+
}
56+
57+
@Override
58+
public void onDestroy(@NonNull LifecycleOwner owner) {
59+
onActivityDestroyed(thisActivity);
60+
}
61+
62+
@Override
63+
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
64+
65+
@Override
66+
public void onActivityStarted(Activity activity) {}
67+
68+
@Override
69+
public void onActivityResumed(Activity activity) {}
70+
71+
@Override
72+
public void onActivityPaused(Activity activity) {}
73+
74+
@Override
75+
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
76+
77+
@Override
78+
public void onActivityDestroyed(Activity activity) {
79+
if (thisActivity == activity && activity.getApplicationContext() != null) {
80+
((Application) activity.getApplicationContext())
81+
.unregisterActivityLifecycleCallbacks(
82+
this); // Use getApplicationContext() to avoid casting failures
83+
}
84+
}
85+
86+
@Override
87+
public void onActivityStopped(Activity activity) {
88+
if (thisActivity == activity) {
89+
delegate.saveStateBeforeResult();
90+
}
91+
}
92+
}
2093

2194
static final String METHOD_CALL_IMAGE = "pickImage";
2295
static final String METHOD_CALL_VIDEO = "pickVideo";
@@ -27,82 +100,130 @@ public class ImagePickerPlugin implements MethodChannel.MethodCallHandler {
27100
private static final int SOURCE_CAMERA = 0;
28101
private static final int SOURCE_GALLERY = 1;
29102

30-
private final PluginRegistry.Registrar registrar;
103+
private MethodChannel channel;
31104
private ImagePickerDelegate delegate;
32-
private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
105+
private FlutterPluginBinding pluginBinding;
106+
private ActivityPluginBinding activityBinding;
107+
private Application application;
108+
private Activity activity;
109+
// This is null when not using v2 embedding;
110+
private Lifecycle lifecycle;
111+
private LifeCycleObserver observer;
33112

34113
public static void registerWith(PluginRegistry.Registrar registrar) {
35114
if (registrar.activity() == null) {
36115
// If a background flutter view tries to register the plugin, there will be no activity from the registrar,
37116
// we stop the registering process immediately because the ImagePicker requires an activity.
38117
return;
39118
}
40-
final ImagePickerCache cache = new ImagePickerCache(registrar.activity());
119+
Activity activity = registrar.activity();
120+
Application application = null;
121+
if (registrar.context() != null) {
122+
application = (Application) (registrar.context().getApplicationContext());
123+
}
124+
ImagePickerPlugin plugin = new ImagePickerPlugin();
125+
plugin.setup(registrar.messenger(), application, activity, registrar, null);
126+
}
41127

42-
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL);
128+
/**
129+
* Default constructor for the plugin.
130+
*
131+
* <p>Use this constructor for production code.
132+
*/
133+
// See also: * {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
134+
public ImagePickerPlugin() {}
43135

44-
final File externalFilesDirectory =
45-
registrar.activity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
46-
final ExifDataCopier exifDataCopier = new ExifDataCopier();
47-
final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
48-
final ImagePickerDelegate delegate =
49-
new ImagePickerDelegate(registrar.activity(), externalFilesDirectory, imageResizer, cache);
136+
@VisibleForTesting
137+
ImagePickerPlugin(final ImagePickerDelegate delegate, final Activity activity) {
138+
this.delegate = delegate;
139+
this.activity = activity;
140+
}
50141

51-
registrar.addActivityResultListener(delegate);
52-
registrar.addRequestPermissionsResultListener(delegate);
53-
final ImagePickerPlugin instance = new ImagePickerPlugin(registrar, delegate);
54-
channel.setMethodCallHandler(instance);
142+
@Override
143+
public void onAttachedToEngine(FlutterPluginBinding binding) {
144+
pluginBinding = binding;
55145
}
56146

57-
@VisibleForTesting
58-
ImagePickerPlugin(final PluginRegistry.Registrar registrar, final ImagePickerDelegate delegate) {
59-
this.registrar = registrar;
60-
this.delegate = delegate;
61-
this.activityLifecycleCallbacks =
62-
new Application.ActivityLifecycleCallbacks() {
63-
@Override
64-
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
147+
@Override
148+
public void onDetachedFromEngine(FlutterPluginBinding binding) {
149+
pluginBinding = null;
150+
}
65151

66-
@Override
67-
public void onActivityStarted(Activity activity) {}
152+
@Override
153+
public void onAttachedToActivity(ActivityPluginBinding binding) {
154+
activityBinding = binding;
155+
setup(
156+
pluginBinding.getBinaryMessenger(),
157+
(Application) pluginBinding.getApplicationContext(),
158+
activityBinding.getActivity(),
159+
null,
160+
activityBinding);
161+
}
68162

69-
@Override
70-
public void onActivityResumed(Activity activity) {}
163+
@Override
164+
public void onDetachedFromActivity() {
165+
tearDown();
166+
}
71167

72-
@Override
73-
public void onActivityPaused(Activity activity) {}
168+
@Override
169+
public void onDetachedFromActivityForConfigChanges() {
170+
onDetachedFromActivity();
171+
}
74172

75-
@Override
76-
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
77-
if (activity == registrar.activity()) {
78-
delegate.saveStateBeforeResult();
79-
}
80-
}
81-
82-
@Override
83-
public void onActivityDestroyed(Activity activity) {
84-
if (activity == registrar.activity()
85-
&& registrar.activity().getApplicationContext() != null) {
86-
((Application) registrar.activity().getApplicationContext())
87-
.unregisterActivityLifecycleCallbacks(
88-
this); // Use getApplicationContext() to avoid casting failures
89-
}
90-
}
91-
92-
@Override
93-
public void onActivityStopped(Activity activity) {}
94-
};
95-
96-
if (this.registrar != null
97-
&& this.registrar.context() != null
98-
&& this.registrar.context().getApplicationContext() != null) {
99-
((Application) this.registrar.context().getApplicationContext())
100-
.registerActivityLifecycleCallbacks(
101-
this
102-
.activityLifecycleCallbacks); // Use getApplicationContext() to avoid casting failures.
173+
@Override
174+
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
175+
onAttachedToActivity(binding);
176+
}
177+
178+
private void setup(
179+
final BinaryMessenger messenger,
180+
final Application application,
181+
final Activity activity,
182+
final PluginRegistry.Registrar registrar,
183+
final ActivityPluginBinding activityBinding) {
184+
this.activity = activity;
185+
this.application = application;
186+
this.delegate = constructDelegate(activity);
187+
channel = new MethodChannel(messenger, CHANNEL);
188+
channel.setMethodCallHandler(this);
189+
observer = new LifeCycleObserver(activity);
190+
if (registrar != null) {
191+
// V1 embedding setup for activity listeners.
192+
application.registerActivityLifecycleCallbacks(observer);
193+
registrar.addActivityResultListener(delegate);
194+
registrar.addRequestPermissionsResultListener(delegate);
195+
} else {
196+
// V2 embedding setup for activity listeners.
197+
activityBinding.addActivityResultListener(delegate);
198+
activityBinding.addRequestPermissionsResultListener(delegate);
199+
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding);
200+
lifecycle.addObserver(observer);
103201
}
104202
}
105203

204+
private void tearDown() {
205+
activityBinding.removeActivityResultListener(delegate);
206+
activityBinding.removeRequestPermissionsResultListener(delegate);
207+
activityBinding = null;
208+
lifecycle.removeObserver(observer);
209+
lifecycle = null;
210+
delegate = null;
211+
channel.setMethodCallHandler(null);
212+
channel = null;
213+
application.unregisterActivityLifecycleCallbacks(observer);
214+
application = null;
215+
}
216+
217+
private final ImagePickerDelegate constructDelegate(final Activity setupActivity) {
218+
final ImagePickerCache cache = new ImagePickerCache(setupActivity);
219+
220+
final File externalFilesDirectory =
221+
setupActivity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
222+
final ExifDataCopier exifDataCopier = new ExifDataCopier();
223+
final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier);
224+
return new ImagePickerDelegate(setupActivity, externalFilesDirectory, imageResizer, cache);
225+
}
226+
106227
// MethodChannel.Result wrapper that responds on the platform thread.
107228
private static class MethodResultWrapper implements MethodChannel.Result {
108229
private MethodChannel.Result methodResult;
@@ -150,7 +271,7 @@ public void run() {
150271

151272
@Override
152273
public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) {
153-
if (registrar.activity() == null) {
274+
if (activity == null) {
154275
rawResult.error("no_activity", "image_picker plugin requires a foreground activity.", null);
155276
return;
156277
}

packages/image_picker/example/android/app/src/main/AndroidManifest.xml

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
<uses-permission android:name="android.permission.INTERNET"/>
55

66
<application android:name="io.flutter.app.FlutterApplication" android:label="Image Picker Example" android:icon="@mipmap/ic_launcher">
7-
<activity android:name=".MainActivity"
8-
android:launchMode="singleTop"
7+
<activity android:name="io.flutter.embedding.android.FlutterActivity"
98
android:theme="@android:style/Theme.Black.NoTitleBar"
109
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
1110
android:hardwareAccelerated="true"
@@ -15,5 +14,12 @@
1514
<category android:name="android.intent.category.LAUNCHER"/>
1615
</intent-filter>
1716
</activity>
17+
<activity
18+
android:name=".EmbeddingV1Activity"
19+
android:theme="@android:style/Theme.Black.NoTitleBar"
20+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
21+
android:hardwareAccelerated="true"
22+
android:windowSoftInputMode="adjustResize">
23+
</activity>
1824
</application>
1925
</manifest>
+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 The Chromium Authors. All rights reserved.
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

@@ -8,8 +8,7 @@
88
import io.flutter.app.FlutterActivity;
99
import io.flutter.plugins.GeneratedPluginRegistrant;
1010

11-
public class MainActivity extends FlutterActivity {
12-
11+
public class EmbeddingV1Activity extends FlutterActivity {
1312
@Override
1413
protected void onCreate(Bundle savedInstanceState) {
1514
super.onCreate(savedInstanceState);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2019 The Chromium 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.imagepickerexample;
6+
7+
import androidx.test.rule.ActivityTestRule;
8+
import dev.flutter.plugins.e2e.FlutterRunner;
9+
import org.junit.Rule;
10+
import org.junit.runner.RunWith;
11+
12+
@RunWith(FlutterRunner.class)
13+
public class EmbeddingV1ActivityTest {
14+
@Rule
15+
public ActivityTestRule<EmbeddingV1Activity> rule =
16+
new ActivityTestRule<>(EmbeddingV1Activity.class);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.flutter.plugins.imagepickerexample;
2+
3+
import androidx.test.rule.ActivityTestRule;
4+
import dev.flutter.plugins.e2e.FlutterRunner;
5+
import io.flutter.embedding.android.FlutterActivity;
6+
import org.junit.Rule;
7+
import org.junit.runner.RunWith;
8+
9+
@RunWith(FlutterRunner.class)
10+
public class FlutterActivityTest {
11+
@Rule
12+
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
13+
}

0 commit comments

Comments
 (0)