diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 6b19209defc7..497b3954c0d2 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 1.2.3 +* 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 d354f3e235df..609f77851d76 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -129,7 +129,23 @@ class ImagePicker { int? imageQuality, int? limit, bool requestFullMetadata = true, - }) { + }) async { + if (limit != null && limit < 1) { + throw ArgumentError.value(limit, 'limit', 'cannot be lower than 1'); + } + // limit: 1 would fail MultiImagePickerOptions validation (requires >= 2), + // so delegate to pickImage which already handles single-image selection. + if (limit == 1) { + final XFile? image = await pickImage( + source: ImageSource.gallery, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ); + return [if (image != null) image]; + } + final imageOptions = ImageOptions.createAndValidate( maxWidth: maxWidth, maxHeight: maxHeight, @@ -242,7 +258,22 @@ class ImagePicker { int? imageQuality, int? limit, bool requestFullMetadata = true, - }) { + }) async { + if (limit != null && limit < 1) { + throw ArgumentError.value(limit, 'limit', 'cannot be lower than 1'); + } + // limit: 1 would fail MediaOptions validation (requires >= 2), + // so delegate to pickMedia which already handles single-item selection. + if (limit == 1) { + final XFile? media = await pickMedia( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ); + return [if (media != null) media]; + } + return platform.getMedia( options: MediaOptions.createAndValidate( allowMultiple: true, diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 137e6acbbd0c..2d87dc6fd567 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 1.2.2 +version: 1.2.3 environment: sdk: ^3.10.0 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 c38334eba92c..98165ae89475 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -610,13 +610,52 @@ void main() { expect(() => picker.pickMultiImage(maxHeight: -1.0), throwsArgumentError); }); - 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); + }); + + 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); - expect(() => picker.pickMultiImage(limit: 1), throwsArgumentError); + 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 { @@ -968,13 +1007,58 @@ void main() { expect(() => picker.pickMultipleMedia(maxHeight: -1.0), throwsArgumentError); }); - 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), throwsArgumentError); expect(() => picker.pickMultipleMedia(limit: 0), 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 => []); - expect(() => picker.pickMultipleMedia(limit: 1), throwsArgumentError); + 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 {