diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 6b19209defc7..0c77264a14e3 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,5 +1,7 @@ ## NEXT +* Fixes `pickMultiImage(limit: 1)` and `pickMultipleMedia(limit: 1)` throwing an `ArgumentError` by delegating to + single-item pickers when `limit` is exactly 1, since the platform interface requires `limit >= 2` for multi-selection. * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. ## 1.2.2 diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 95fcd19120b9..c8604a617769 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -133,6 +133,18 @@ class ImagePicker { int? limit, bool requestFullMetadata = true, }) { + // limit: 1 would fail MultiImagePickerOptions validation (requires >= 2), + // so delegate to pickImage which already handles single-image selection. + if (limit == 1) { + return pickImage( + source: ImageSource.gallery, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ).then((XFile? image) => image == null ? [] : [image]); + } + final imageOptions = ImageOptions.createAndValidate( maxWidth: maxWidth, maxHeight: maxHeight, @@ -249,6 +261,17 @@ class ImagePicker { int? limit, bool requestFullMetadata = true, }) { + // limit: 1 would fail MediaOptions validation (requires >= 2), + // so delegate to pickMedia which already handles single-item selection. + if (limit == 1) { + return pickMedia( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ).then((XFile? media) => media == null ? [] : [media]); + } + return platform.getMedia( options: MediaOptions.createAndValidate( allowMultiple: true, diff --git a/packages/image_picker/image_picker/test/image_picker_test.dart b/packages/image_picker/image_picker/test/image_picker_test.dart index 6230481169f3..91dfedfa0f39 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -709,15 +709,62 @@ void main() { ); }); - test('does not accept a limit argument lower than 2', () { + test('does not accept a limit argument lower than 1', () { final picker = ImagePicker(); expect(() => picker.pickMultiImage(limit: -1), throwsArgumentError); expect(() => picker.pickMultiImage(limit: 0), throwsArgumentError); - - expect(() => picker.pickMultiImage(limit: 1), throwsArgumentError); }); + test( + 'delegates to pickImage when limit is 1 and image is picked', + () async { + when( + mockPlatform.getImageFromSource( + source: anyNamed('source'), + options: anyNamed('options'), + ), + ).thenAnswer( + (Invocation _) async => XFile('test_path', name: 'test.jpg'), + ); + + final picker = ImagePicker(); + final List result = await picker.pickMultiImage(limit: 1); + + expect(result, hasLength(1)); + expect(result.first.path, 'test_path'); + verify( + mockPlatform.getImageFromSource( + source: ImageSource.gallery, + options: anyNamed('options'), + ), + ); + }, + ); + + test( + 'delegates to pickImage when limit is 1 and no image is picked', + () async { + when( + mockPlatform.getImageFromSource( + source: anyNamed('source'), + options: anyNamed('options'), + ), + ).thenAnswer((Invocation _) async => null); + + final picker = ImagePicker(); + final List result = await picker.pickMultiImage(limit: 1); + + expect(result, isEmpty); + verify( + mockPlatform.getImageFromSource( + source: ImageSource.gallery, + options: anyNamed('options'), + ), + ); + }, + ); + test('handles an empty image file response gracefully', () async { final picker = ImagePicker(); @@ -1104,7 +1151,7 @@ void main() { ); }); - test('does not accept a limit argument lower than 2', () { + test('does not accept a limit argument lower than 1', () { final picker = ImagePicker(); expect( () => picker.pickMultipleMedia(limit: -1), @@ -1112,10 +1159,61 @@ void main() { ); expect(() => picker.pickMultipleMedia(limit: 0), throwsArgumentError); - - expect(() => picker.pickMultipleMedia(limit: 1), throwsArgumentError); }); + test( + 'delegates to pickMedia when limit is 1 and media is picked', + () async { + when( + mockPlatform.getMedia(options: anyNamed('options')), + ).thenAnswer((Invocation _) async => [XFile('test_path')]); + + final picker = ImagePicker(); + final List result = await picker.pickMultipleMedia(limit: 1); + + expect(result, hasLength(1)); + expect(result.first.path, 'test_path'); + verify( + mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaOptions options) => options.allowMultiple, + 'allowMultiple', + isFalse, + ), + named: 'options', + ), + ), + ); + }, + ); + + test( + 'delegates to pickMedia when limit is 1 and no media is picked', + () async { + when( + mockPlatform.getMedia(options: anyNamed('options')), + ).thenAnswer((Invocation _) async => []); + + final picker = ImagePicker(); + final List result = await picker.pickMultipleMedia(limit: 1); + + expect(result, isEmpty); + verify( + mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaOptions options) => options.allowMultiple, + 'allowMultiple', + isFalse, + ), + named: 'options', + ), + ), + ); + }, + ); + test('handles an empty image file response gracefully', () async { final picker = ImagePicker();