Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.13+17

* Updates plugin to use Android Photo Picker on API 36 and above.

## 0.8.13+16

* Bumps androidx.core:core from 1.17.0 to 1.18.0.
Expand Down
21 changes: 17 additions & 4 deletions packages/image_picker/image_picker_android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ should add it to your `pubspec.yaml` as usual.

## Photo Picker

On Android 13 and above this packages uses the Android Photo Picker.
On **Android 16** (API level 36) and above, gallery image, video, and mixed-media
picks always use the Android Photo Picker. [`ImagePickerAndroid.useAndroidPhotoPicker`][4]
cannot be set to `false` to use the legacy `ACTION_GET_CONTENT` flow on those
versions; it would often return no path. See [flutter/flutter#182071][5].
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have evidence that it "often" returns no path, but as with the docs we don't need to justify this in the README, just explain it.


On Android 12 and below this package has optional Android Photo Picker functionality.
On Android 13 through 15, the default is to use the legacy picker; you can opt in to
the Photo Picker with the flag below.

To use this feature, add the following code to your app before calling any `image_picker` APIs:
On Android 12 and below this package has optional Android Photo Picker functionality
(when the flag is `true` and the system supports it).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me what distinction is being made between <=12 and 13-15. Don't they have the same behavior of being opt-in?

(IIRC the distinction in the old text between 13+ and <13 is incorrect, and dates to before we made it optional everywhere due to limitations.)


To opt in on versions where it is optional, add the following code to your app before
calling any `image_picker` APIs:

<?code-excerpt "main.dart (photo-picker-example)"?>
```dart
Expand All @@ -33,8 +41,13 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
}
```

In addition, `ImagePickerAndroid.useAndroidPhotoPicker` must be set to `true` to use the `limit` functionality. It is implemented based on [`ActivityResultContract`][3], so it can only be ensured to take effect on Android 13 or above. Otherwise, it depends on whether the corresponding system app supports it.
In addition, `ImagePickerAndroid.useAndroidPhotoPicker` must be set to `true` to use
the `limit` functionality. It is implemented based on [`ActivityResultContract`][3], so
it can only be ensured to take effect on Android 13 or above. Otherwise, it depends on
whether the corresponding system app supports it.

[1]: https://pub.dev/packages/image_picker
[2]: https://flutter.dev/to/endorsed-federated-plugin
[3]: https://developer.android.google.cn/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts.PickMultipleVisualMedia
[4]: https://pub.dev/documentation/image_picker_android/latest/image_picker_android/ImagePickerAndroid/useAndroidPhotoPicker.html
[5]: https://github.com/flutter/flutter/issues/182071
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ public void chooseMediaFromGallery(

private void launchPickMediaFromGalleryIntent(Messages.GeneralOptions generalOptions) {
Intent pickMediaIntent;
if (generalOptions.getUsePhotoPicker()) {
if (ImagePickerUtils.effectiveUsePhotoPicker(generalOptions.getUsePhotoPicker())) {
if (generalOptions.getAllowMultiple()) {
int limit = ImagePickerUtils.getLimitFromOption(generalOptions);

Expand Down Expand Up @@ -342,7 +342,7 @@ public void chooseVideoFromGallery(

private void launchPickVideoFromGalleryIntent(Boolean usePhotoPicker) {
Intent pickVideoIntent;
if (usePhotoPicker) {
if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) {
pickVideoIntent =
new ActivityResultContracts.PickVisualMedia()
.createIntent(
Expand Down Expand Up @@ -441,7 +441,7 @@ public void chooseMultiImageFromGallery(

private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) {
Intent pickImageIntent;
if (usePhotoPicker) {
if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) {
pickImageIntent =
new ActivityResultContracts.PickVisualMedia()
.createIntent(
Expand All @@ -458,7 +458,7 @@ private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) {

private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker, int limit) {
Intent pickMultiImageIntent;
if (usePhotoPicker) {
if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) {
pickMultiImageIntent =
new ActivityResultContracts.PickMultipleVisualMedia(limit)
.createIntent(
Expand Down Expand Up @@ -490,7 +490,7 @@ public void chooseMultiVideoFromGallery(

private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int limit) {
Intent pickMultiVideoIntent;
if (usePhotoPicker) {
if (ImagePickerUtils.effectiveUsePhotoPicker(usePhotoPicker)) {
pickMultiVideoIntent =
new ActivityResultContracts.PickMultipleVisualMedia(limit)
.createIntent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.Arrays;

final class ImagePickerUtils {
private static final int API_LEVEL_36 = 36;
Comment thread
ashutosh2014 marked this conversation as resolved.

/** returns true, if permission present in manifest, otherwise false */
private static boolean isPermissionPresentInManifest(Context context, String permissionName) {
try {
Expand Down Expand Up @@ -86,4 +88,22 @@ static int getLimitFromOption(Messages.GeneralOptions generalOptions) {

return effectiveLimit;
}

/**
* Returns whether gallery/media selection should use {@link
* androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia} (Android Photo
* Picker) instead of {@link android.content.Intent#ACTION_GET_CONTENT}.
*
* <p>On Android API 36+, {@code ACTION_GET_CONTENT} for images may be handled by the system
* photo picker's {@code PhotopickerGetContentActivity}. That path combined with {@code
* startActivityForResult} can return {@link android.app.Activity#RESULT_OK} without {@link
* android.content.Intent#getData()} or usable {@link android.content.ClipData}, so the plugin
* would complete with no paths. The {@code PickVisualMedia} contract uses the Activity Result API
* and receives URIs reliably.
*
* <p>See <a href="https://github.com/flutter/flutter/issues/182071">flutter/flutter#182071</a>.
*/
static boolean effectiveUsePhotoPicker(boolean usePhotoPickerFromDart) {
return Build.VERSION.SDK_INT >= API_LEVEL_36 || usePhotoPickerFromDart;
}
Comment thread
ashutosh2014 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.imagepicker;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
public class ImagePickerUtilsTest {

@Test
@Config(sdk = 35)
public void effectiveUsePhotoPicker_belowApi36_usesDartPreference() {
assertFalse(ImagePickerUtils.effectiveUsePhotoPicker(false));
assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(true));
}

@Test
@Config(sdk = 36)
public void effectiveUsePhotoPicker_onApi36_alwaysUsesPhotoPicker() {
assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(false));
assertTrue(ImagePickerUtils.effectiveUsePhotoPicker(true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@ class ImagePickerAndroid extends ImagePickerPlatform {

final ImagePickerApi _hostApi;

/// Sets [ImagePickerAndroid] to use Android 13 Photo Picker.
/// Whether gallery picks use the Android Photo Picker instead of the legacy
/// document picker (`ACTION_GET_CONTENT`).
///
/// Currently defaults to false, but the default is subject to change.
/// Defaults to `false` (the default may change in a future release). When `true`,
/// the plugin uses the Photo Picker on Android versions where it is available for
/// the requested operation.
///
/// **Android 16** (API level 36) and higher: the plugin always uses the Photo Picker
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is critical information that should be summarized in the first sentence of the docs. I would expect something like:

/// Whether to use the new Photo Picker on Android versions less than 16.
///
/// On Android 16+, the Photo Picker is always used regardless of the state of this flag.
///
/// On Android <16, if this is false the legacy picker (`ACTION_GET_CONTENT`) will be used
/// instead of the newer Photo Picker. It currently defaults to false, but the default is subject
/// to change.

/// for gallery image, video, and mixed-media selection. This field is **not**
/// respected for opting out on those versions, because the legacy flow can return
/// success without a usable file URI. See
/// https://github.com/flutter/flutter/issues/182071
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-facing docs don't need to explain the history of the change, just the behavior.

///
/// Must be `true` to use the multi-select `limit` parameter on Android 13 and
/// above, where that feature is implemented via the Photo Picker.
bool useAndroidPhotoPicker = false;

/// Registers this class as the default platform implementation.
Expand Down
2 changes: 1 addition & 1 deletion packages/image_picker/image_picker_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: image_picker_android
description: Android implementation of the image_picker plugin.
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 0.8.13+16
version: 0.8.13+17

environment:
sdk: ^3.9.0
Expand Down