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].

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).

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;

/** 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;
}
}
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
/// 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
///
/// 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