Skip to content

Commit 43f1979

Browse files
authored
[image_picker] Add limit parameter to pickMultiImage and pickMultipleMedia to ios and Android (flutter#6201)
Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` and supports its use on iOS and Android. The `limit` argument defines how many images or media files can be select. Fixes: [flutter/flutter#85772](flutter/flutter#85772)
1 parent 554f936 commit 43f1979

35 files changed

+1111
-366
lines changed

packages/image_picker/image_picker/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.1.0
2+
3+
* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` which limits
4+
the number of media that can be selected.
5+
* Currently supported only on iOS and Android.
6+
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
7+
18
## 1.0.8
29

310
* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1.

packages/image_picker/image_picker/example/lib/main.dart

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class _MyHomePageState extends State<MyHomePage> {
5656
final TextEditingController maxWidthController = TextEditingController();
5757
final TextEditingController maxHeightController = TextEditingController();
5858
final TextEditingController qualityController = TextEditingController();
59+
final TextEditingController limitController = TextEditingController();
5960

6061
Future<void> _playVideo(XFile? file) async {
6162
if (file != null && mounted) {
@@ -96,19 +97,21 @@ class _MyHomePageState extends State<MyHomePage> {
9697
source: source, maxDuration: const Duration(seconds: 10));
9798
await _playVideo(file);
9899
} else if (isMultiImage) {
99-
await _displayPickImageDialog(context,
100-
(double? maxWidth, double? maxHeight, int? quality) async {
100+
await _displayPickImageDialog(context, true, (double? maxWidth,
101+
double? maxHeight, int? quality, int? limit) async {
101102
try {
102103
final List<XFile> pickedFileList = isMedia
103104
? await _picker.pickMultipleMedia(
104105
maxWidth: maxWidth,
105106
maxHeight: maxHeight,
106107
imageQuality: quality,
108+
limit: limit,
107109
)
108110
: await _picker.pickMultiImage(
109111
maxWidth: maxWidth,
110112
maxHeight: maxHeight,
111113
imageQuality: quality,
114+
limit: limit,
112115
);
113116
setState(() {
114117
_mediaFileList = pickedFileList;
@@ -120,8 +123,8 @@ class _MyHomePageState extends State<MyHomePage> {
120123
}
121124
});
122125
} else if (isMedia) {
123-
await _displayPickImageDialog(context,
124-
(double? maxWidth, double? maxHeight, int? quality) async {
126+
await _displayPickImageDialog(context, false, (double? maxWidth,
127+
double? maxHeight, int? quality, int? limit) async {
125128
try {
126129
final List<XFile> pickedFileList = <XFile>[];
127130
final XFile? media = await _picker.pickMedia(
@@ -142,8 +145,8 @@ class _MyHomePageState extends State<MyHomePage> {
142145
}
143146
});
144147
} else {
145-
await _displayPickImageDialog(context,
146-
(double? maxWidth, double? maxHeight, int? quality) async {
148+
await _displayPickImageDialog(context, false, (double? maxWidth,
149+
double? maxHeight, int? quality, int? limit) async {
147150
try {
148151
final XFile? pickedFile = await _picker.pickImage(
149152
source: source,
@@ -454,7 +457,7 @@ class _MyHomePageState extends State<MyHomePage> {
454457
}
455458

456459
Future<void> _displayPickImageDialog(
457-
BuildContext context, OnPickImageCallback onPick) async {
460+
BuildContext context, bool isMulti, OnPickImageCallback onPick) async {
458461
return showDialog(
459462
context: context,
460463
builder: (BuildContext context) {
@@ -483,6 +486,13 @@ class _MyHomePageState extends State<MyHomePage> {
483486
decoration: const InputDecoration(
484487
hintText: 'Enter quality if desired'),
485488
),
489+
if (isMulti)
490+
TextField(
491+
controller: limitController,
492+
keyboardType: TextInputType.number,
493+
decoration: const InputDecoration(
494+
hintText: 'Enter limit if desired'),
495+
),
486496
],
487497
),
488498
actions: <Widget>[
@@ -504,7 +514,10 @@ class _MyHomePageState extends State<MyHomePage> {
504514
final int? quality = qualityController.text.isNotEmpty
505515
? int.parse(qualityController.text)
506516
: null;
507-
onPick(width, height, quality);
517+
final int? limit = limitController.text.isNotEmpty
518+
? int.parse(limitController.text)
519+
: null;
520+
onPick(width, height, quality, limit);
508521
Navigator.of(context).pop();
509522
}),
510523
],
@@ -514,7 +527,7 @@ class _MyHomePageState extends State<MyHomePage> {
514527
}
515528

516529
typedef OnPickImageCallback = void Function(
517-
double? maxWidth, double? maxHeight, int? quality);
530+
double? maxWidth, double? maxHeight, int? quality, int? limit);
518531

519532
class AspectRatioVideo extends StatefulWidget {
520533
const AspectRatioVideo(this.controller, {super.key});

packages/image_picker/image_picker/example/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ description: Demonstrates how to use the image_picker plugin.
33
publish_to: none
44

55
environment:
6-
sdk: ^3.1.0
7-
flutter: ">=3.13.0"
6+
sdk: ^3.3.0
7+
flutter: ">=3.19.0"
88

99
dependencies:
1010
flutter:
@@ -17,7 +17,7 @@ dependencies:
1717
# The example app is bundled with the plugin so we use a path dependency on
1818
# the parent directory to use the current plugin's version.
1919
path: ../
20-
image_picker_platform_interface: ^2.8.0
20+
image_picker_platform_interface: ^2.10.0
2121
mime: ^1.0.4
2222
video_player: ^2.7.0
2323

packages/image_picker/image_picker/lib/image_picker.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class ImagePicker {
128128
double? maxWidth,
129129
double? maxHeight,
130130
int? imageQuality,
131+
int? limit,
131132
bool requestFullMetadata = true,
132133
}) {
133134
final ImageOptions imageOptions = ImageOptions.createAndValidate(
@@ -138,8 +139,9 @@ class ImagePicker {
138139
);
139140

140141
return platform.getMultiImageWithOptions(
141-
options: MultiImagePickerOptions(
142+
options: MultiImagePickerOptions.createAndValidate(
142143
imageOptions: imageOptions,
144+
limit: limit,
143145
),
144146
);
145147
}
@@ -186,7 +188,7 @@ class ImagePicker {
186188
bool requestFullMetadata = true,
187189
}) async {
188190
final List<XFile> listMedia = await platform.getMedia(
189-
options: MediaOptions(
191+
options: MediaOptions.createAndValidate(
190192
imageOptions: ImageOptions.createAndValidate(
191193
maxHeight: maxHeight,
192194
maxWidth: maxWidth,
@@ -239,17 +241,19 @@ class ImagePicker {
239241
double? maxWidth,
240242
double? maxHeight,
241243
int? imageQuality,
244+
int? limit,
242245
bool requestFullMetadata = true,
243246
}) {
244247
return platform.getMedia(
245-
options: MediaOptions(
248+
options: MediaOptions.createAndValidate(
246249
allowMultiple: true,
247250
imageOptions: ImageOptions.createAndValidate(
248251
maxHeight: maxHeight,
249252
maxWidth: maxWidth,
250253
imageQuality: imageQuality,
251254
requestFullMetadata: requestFullMetadata,
252255
),
256+
limit: limit,
253257
),
254258
);
255259
}

packages/image_picker/image_picker/pubspec.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ description: Flutter plugin for selecting images from the Android and iOS image
33
library, and taking new pictures with the camera.
44
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
6-
version: 1.0.8
6+
version: 1.1.0
77

88
environment:
9-
sdk: ^3.1.0
10-
flutter: ">=3.13.0"
9+
sdk: ^3.3.0
10+
flutter: ">=3.19.0"
1111

1212
flutter:
1313
plugin:
@@ -30,10 +30,10 @@ dependencies:
3030
sdk: flutter
3131
image_picker_android: ^0.8.7
3232
image_picker_for_web: ">=2.2.0 <4.0.0"
33-
image_picker_ios: ^0.8.9+1
33+
image_picker_ios: ^0.8.8
3434
image_picker_linux: ^0.2.1
3535
image_picker_macos: ^0.2.1
36-
image_picker_platform_interface: ^2.8.0
36+
image_picker_platform_interface: ^2.10.0
3737
image_picker_windows: ^0.2.1
3838

3939
dev_dependencies:

packages/image_picker/image_picker/test/image_picker_test.dart

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,16 @@ void main() {
431431
imageQuality: 70,
432432
);
433433
await picker.pickMultiImage(
434-
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
434+
maxWidth: 10.0,
435+
maxHeight: 20.0,
436+
imageQuality: 70,
437+
);
438+
await picker.pickMultiImage(
439+
maxWidth: 10.0,
440+
maxHeight: 20.0,
441+
imageQuality: 70,
442+
limit: 5,
443+
);
435444

436445
verifyInOrder(<Object>[
437446
mockPlatform.getMultiImageWithOptions(
@@ -529,6 +538,29 @@ void main() {
529538
named: 'options',
530539
),
531540
),
541+
mockPlatform.getMultiImageWithOptions(
542+
options: argThat(
543+
isInstanceOf<MultiImagePickerOptions>()
544+
.having(
545+
(MultiImagePickerOptions options) =>
546+
options.imageOptions.maxWidth,
547+
'maxWidth',
548+
equals(10.0))
549+
.having(
550+
(MultiImagePickerOptions options) =>
551+
options.imageOptions.maxWidth,
552+
'maxHeight',
553+
equals(10.0))
554+
.having(
555+
(MultiImagePickerOptions options) =>
556+
options.imageOptions.imageQuality,
557+
'imageQuality',
558+
equals(70))
559+
.having((MultiImagePickerOptions options) => options.limit,
560+
'limit', equals(5)),
561+
named: 'options',
562+
),
563+
),
532564
]);
533565
});
534566

@@ -545,6 +577,24 @@ void main() {
545577
);
546578
});
547579

580+
test('does not accept a limit argument lower than 2', () {
581+
final ImagePicker picker = ImagePicker();
582+
expect(
583+
() => picker.pickMultiImage(limit: -1),
584+
throwsArgumentError,
585+
);
586+
587+
expect(
588+
() => picker.pickMultiImage(limit: 0),
589+
throwsArgumentError,
590+
);
591+
592+
expect(
593+
() => picker.pickMultiImage(limit: 1),
594+
throwsArgumentError,
595+
);
596+
});
597+
548598
test('handles an empty image file response gracefully', () async {
549599
final ImagePicker picker = ImagePicker();
550600

@@ -620,7 +670,15 @@ void main() {
620670
imageQuality: 70,
621671
);
622672
await picker.pickMedia(
623-
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
673+
maxWidth: 10.0,
674+
maxHeight: 20.0,
675+
imageQuality: 70,
676+
);
677+
await picker.pickMedia(
678+
maxWidth: 10.0,
679+
maxHeight: 20.0,
680+
imageQuality: 70,
681+
);
624682

625683
verifyInOrder(<Object>[
626684
mockPlatform.getMedia(
@@ -793,7 +851,16 @@ void main() {
793851
imageQuality: 70,
794852
);
795853
await picker.pickMultipleMedia(
796-
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
854+
maxWidth: 10.0,
855+
maxHeight: 20.0,
856+
imageQuality: 70,
857+
);
858+
await picker.pickMultipleMedia(
859+
maxWidth: 10.0,
860+
maxHeight: 20.0,
861+
imageQuality: 70,
862+
limit: 5,
863+
);
797864

798865
verifyInOrder(<Object>[
799866
mockPlatform.getMedia(
@@ -885,6 +952,27 @@ void main() {
885952
named: 'options',
886953
),
887954
),
955+
mockPlatform.getMedia(
956+
options: argThat(
957+
isInstanceOf<MediaOptions>()
958+
.having(
959+
(MediaOptions options) => options.imageOptions.maxWidth,
960+
'maxWidth',
961+
equals(10.0))
962+
.having(
963+
(MediaOptions options) => options.imageOptions.maxWidth,
964+
'maxHeight',
965+
equals(10.0))
966+
.having(
967+
(MediaOptions options) =>
968+
options.imageOptions.imageQuality,
969+
'imageQuality',
970+
equals(70))
971+
.having((MediaOptions options) => options.limit, 'limit',
972+
equals(5)),
973+
named: 'options',
974+
),
975+
),
888976
]);
889977
});
890978

@@ -901,6 +989,24 @@ void main() {
901989
);
902990
});
903991

992+
test('does not accept a limit argument lower than 2', () {
993+
final ImagePicker picker = ImagePicker();
994+
expect(
995+
() => picker.pickMultipleMedia(limit: -1),
996+
throwsArgumentError,
997+
);
998+
999+
expect(
1000+
() => picker.pickMultipleMedia(limit: 0),
1001+
throwsArgumentError,
1002+
);
1003+
1004+
expect(
1005+
() => picker.pickMultipleMedia(limit: 1),
1006+
throwsArgumentError,
1007+
);
1008+
});
1009+
9041010
test('handles an empty image file response gracefully', () async {
9051011
final ImagePicker picker = ImagePicker();
9061012

packages/image_picker/image_picker_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.8.10
2+
3+
* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected.
4+
15
## 0.8.9+6
26

37
* Updates minSdkVersion to 19.

packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:tools="http://schemas.android.com/tools"
23
package="io.flutter.plugins.imagepicker">
34

45
<application>
@@ -11,5 +12,15 @@
1112
android:name="android.support.FILE_PROVIDER_PATHS"
1213
android:resource="@xml/flutter_image_picker_file_paths" />
1314
</provider>
15+
<!-- Trigger Google Play services to install the backported photo picker module. -->
16+
<service android:name="com.google.android.gms.metadata.ModuleDependencies"
17+
android:enabled="false"
18+
android:exported="false"
19+
tools:ignore="MissingClass">
20+
<intent-filter>
21+
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
22+
</intent-filter>
23+
<meta-data android:name="photopicker_activity:0:required" android:value="" />
24+
</service>
1425
</application>
1526
</manifest>

0 commit comments

Comments
 (0)