[image_picker] Add limit parameter to pickMultiImage and pickMultipleMedia to ios and Android (#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](https://github.com/flutter/flutter/issues/85772)
diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 90f2406..c9f4b3b 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.1.0
+
+* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` which limits
+  the number of media that can be selected. 
+    * Currently supported only on iOS and Android.
+* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
+
 ## 1.0.8
 
 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1.
diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart
index 2e1a3d7..a836c0a 100755
--- a/packages/image_picker/image_picker/example/lib/main.dart
+++ b/packages/image_picker/image_picker/example/lib/main.dart
@@ -56,6 +56,7 @@
   final TextEditingController maxWidthController = TextEditingController();
   final TextEditingController maxHeightController = TextEditingController();
   final TextEditingController qualityController = TextEditingController();
+  final TextEditingController limitController = TextEditingController();
 
   Future<void> _playVideo(XFile? file) async {
     if (file != null && mounted) {
@@ -96,19 +97,21 @@
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, true, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final List<XFile> pickedFileList = isMedia
                 ? await _picker.pickMultipleMedia(
                     maxWidth: maxWidth,
                     maxHeight: maxHeight,
                     imageQuality: quality,
+                    limit: limit,
                   )
                 : await _picker.pickMultiImage(
                     maxWidth: maxWidth,
                     maxHeight: maxHeight,
                     imageQuality: quality,
+                    limit: limit,
                   );
             setState(() {
               _mediaFileList = pickedFileList;
@@ -120,8 +123,8 @@
           }
         });
       } else if (isMedia) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final List<XFile> pickedFileList = <XFile>[];
             final XFile? media = await _picker.pickMedia(
@@ -142,8 +145,8 @@
           }
         });
       } else {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final XFile? pickedFile = await _picker.pickImage(
               source: source,
@@ -454,7 +457,7 @@
   }
 
   Future<void> _displayPickImageDialog(
-      BuildContext context, OnPickImageCallback onPick) async {
+      BuildContext context, bool isMulti, OnPickImageCallback onPick) async {
     return showDialog(
         context: context,
         builder: (BuildContext context) {
@@ -483,6 +486,13 @@
                   decoration: const InputDecoration(
                       hintText: 'Enter quality if desired'),
                 ),
+                if (isMulti)
+                  TextField(
+                    controller: limitController,
+                    keyboardType: TextInputType.number,
+                    decoration: const InputDecoration(
+                        hintText: 'Enter limit if desired'),
+                  ),
               ],
             ),
             actions: <Widget>[
@@ -504,7 +514,10 @@
                     final int? quality = qualityController.text.isNotEmpty
                         ? int.parse(qualityController.text)
                         : null;
-                    onPick(width, height, quality);
+                    final int? limit = limitController.text.isNotEmpty
+                        ? int.parse(limitController.text)
+                        : null;
+                    onPick(width, height, quality, limit);
                     Navigator.of(context).pop();
                   }),
             ],
@@ -514,7 +527,7 @@
 }
 
 typedef OnPickImageCallback = void Function(
-    double? maxWidth, double? maxHeight, int? quality);
+    double? maxWidth, double? maxHeight, int? quality, int? limit);
 
 class AspectRatioVideo extends StatefulWidget {
   const AspectRatioVideo(this.controller, {super.key});
diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml
index b41127e..b7ca08c 100644
--- a/packages/image_picker/image_picker/example/pubspec.yaml
+++ b/packages/image_picker/image_picker/example/pubspec.yaml
@@ -3,8 +3,8 @@
 publish_to: none
 
 environment:
-  sdk: ^3.1.0
-  flutter: ">=3.13.0"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 dependencies:
   flutter:
@@ -17,7 +17,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
   mime: ^1.0.4
   video_player: ^2.7.0
 
diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart
index 0ad4afc..246fe3a 100755
--- a/packages/image_picker/image_picker/lib/image_picker.dart
+++ b/packages/image_picker/image_picker/lib/image_picker.dart
@@ -128,6 +128,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    int? limit,
     bool requestFullMetadata = true,
   }) {
     final ImageOptions imageOptions = ImageOptions.createAndValidate(
@@ -138,8 +139,9 @@
     );
 
     return platform.getMultiImageWithOptions(
-      options: MultiImagePickerOptions(
+      options: MultiImagePickerOptions.createAndValidate(
         imageOptions: imageOptions,
+        limit: limit,
       ),
     );
   }
@@ -186,7 +188,7 @@
     bool requestFullMetadata = true,
   }) async {
     final List<XFile> listMedia = await platform.getMedia(
-      options: MediaOptions(
+      options: MediaOptions.createAndValidate(
         imageOptions: ImageOptions.createAndValidate(
           maxHeight: maxHeight,
           maxWidth: maxWidth,
@@ -239,10 +241,11 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    int? limit,
     bool requestFullMetadata = true,
   }) {
     return platform.getMedia(
-      options: MediaOptions(
+      options: MediaOptions.createAndValidate(
         allowMultiple: true,
         imageOptions: ImageOptions.createAndValidate(
           maxHeight: maxHeight,
@@ -250,6 +253,7 @@
           imageQuality: imageQuality,
           requestFullMetadata: requestFullMetadata,
         ),
+        limit: limit,
       ),
     );
   }
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index a0e3ce1..5854c9f 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -3,11 +3,11 @@
   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.0.8
+version: 1.1.0
 
 environment:
-  sdk: ^3.1.0
-  flutter: ">=3.13.0"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 flutter:
   plugin:
@@ -30,10 +30,10 @@
     sdk: flutter
   image_picker_android: ^0.8.7
   image_picker_for_web: ">=2.2.0 <4.0.0"
-  image_picker_ios: ^0.8.9+1
+  image_picker_ios: ^0.8.8
   image_picker_linux: ^0.2.1
   image_picker_macos: ^0.2.1
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
   image_picker_windows: ^0.2.1
 
 dev_dependencies:
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 4ff5b4e..766b380 100644
--- a/packages/image_picker/image_picker/test/image_picker_test.dart
+++ b/packages/image_picker/image_picker/test/image_picker_test.dart
@@ -431,7 +431,16 @@
             imageQuality: 70,
           );
           await picker.pickMultiImage(
-              maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          );
+          await picker.pickMultiImage(
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+            limit: 5,
+          );
 
           verifyInOrder(<Object>[
             mockPlatform.getMultiImageWithOptions(
@@ -529,6 +538,29 @@
                 named: 'options',
               ),
             ),
+            mockPlatform.getMultiImageWithOptions(
+              options: argThat(
+                isInstanceOf<MultiImagePickerOptions>()
+                    .having(
+                        (MultiImagePickerOptions options) =>
+                            options.imageOptions.maxWidth,
+                        'maxWidth',
+                        equals(10.0))
+                    .having(
+                        (MultiImagePickerOptions options) =>
+                            options.imageOptions.maxWidth,
+                        'maxHeight',
+                        equals(10.0))
+                    .having(
+                        (MultiImagePickerOptions options) =>
+                            options.imageOptions.imageQuality,
+                        'imageQuality',
+                        equals(70))
+                    .having((MultiImagePickerOptions options) => options.limit,
+                        'limit', equals(5)),
+                named: 'options',
+              ),
+            ),
           ]);
         });
 
@@ -545,6 +577,24 @@
           );
         });
 
+        test('does not accept a limit argument lower than 2', () {
+          final ImagePicker picker = ImagePicker();
+          expect(
+            () => picker.pickMultiImage(limit: -1),
+            throwsArgumentError,
+          );
+
+          expect(
+            () => picker.pickMultiImage(limit: 0),
+            throwsArgumentError,
+          );
+
+          expect(
+            () => picker.pickMultiImage(limit: 1),
+            throwsArgumentError,
+          );
+        });
+
         test('handles an empty image file response gracefully', () async {
           final ImagePicker picker = ImagePicker();
 
@@ -620,7 +670,15 @@
             imageQuality: 70,
           );
           await picker.pickMedia(
-              maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          );
+          await picker.pickMedia(
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          );
 
           verifyInOrder(<Object>[
             mockPlatform.getMedia(
@@ -793,7 +851,16 @@
             imageQuality: 70,
           );
           await picker.pickMultipleMedia(
-              maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          );
+          await picker.pickMultipleMedia(
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+            limit: 5,
+          );
 
           verifyInOrder(<Object>[
             mockPlatform.getMedia(
@@ -885,6 +952,27 @@
                 named: 'options',
               ),
             ),
+            mockPlatform.getMedia(
+              options: argThat(
+                isInstanceOf<MediaOptions>()
+                    .having(
+                        (MediaOptions options) => options.imageOptions.maxWidth,
+                        'maxWidth',
+                        equals(10.0))
+                    .having(
+                        (MediaOptions options) => options.imageOptions.maxWidth,
+                        'maxHeight',
+                        equals(10.0))
+                    .having(
+                        (MediaOptions options) =>
+                            options.imageOptions.imageQuality,
+                        'imageQuality',
+                        equals(70))
+                    .having((MediaOptions options) => options.limit, 'limit',
+                        equals(5)),
+                named: 'options',
+              ),
+            ),
           ]);
         });
 
@@ -901,6 +989,24 @@
           );
         });
 
+        test('does not accept a limit argument lower than 2', () {
+          final ImagePicker picker = ImagePicker();
+          expect(
+            () => picker.pickMultipleMedia(limit: -1),
+            throwsArgumentError,
+          );
+
+          expect(
+            () => picker.pickMultipleMedia(limit: 0),
+            throwsArgumentError,
+          );
+
+          expect(
+            () => picker.pickMultipleMedia(limit: 1),
+            throwsArgumentError,
+          );
+        });
+
         test('handles an empty image file response gracefully', () async {
           final ImagePicker picker = ImagePicker();
 
diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md
index 5ffcad5..9efc86c 100644
--- a/packages/image_picker/image_picker_android/CHANGELOG.md
+++ b/packages/image_picker/image_picker_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.10
+
+* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected.
+
 ## 0.8.9+6
 
 * Updates minSdkVersion to 19.
diff --git a/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml b/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml
index 5d1773e..5f9bcd8 100755
--- a/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml
+++ b/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="io.flutter.plugins.imagepicker">
 
     <application>
@@ -11,5 +12,15 @@
                 android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/flutter_image_picker_file_paths" />
         </provider>
+        <!-- Trigger Google Play services to install the backported photo picker module. -->
+        <service android:name="com.google.android.gms.metadata.ModuleDependencies"
+                android:enabled="false"
+                android:exported="false"
+                tools:ignore="MissingClass">
+            <intent-filter>
+                <action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
+            </intent-filter>
+            <meta-data android:name="photopicker_activity:0:required" android:value="" />
+        </service>
     </application>
 </manifest>
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
index 98b5560..96bf727 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
@@ -297,8 +297,10 @@
     Intent pickMediaIntent;
     if (generalOptions.getUsePhotoPicker()) {
       if (generalOptions.getAllowMultiple()) {
+        int limit = ImagePickerUtils.getLimitFromOption(generalOptions);
+
         pickMediaIntent =
-            new ActivityResultContracts.PickMultipleVisualMedia()
+            new ActivityResultContracts.PickMultipleVisualMedia(limit)
                 .createIntent(
                     activity,
                     new PickVisualMediaRequest.Builder()
@@ -426,13 +428,14 @@
   public void chooseMultiImageFromGallery(
       @NonNull ImageSelectionOptions options,
       boolean usePhotoPicker,
+      int limit,
       @NonNull Messages.Result<List<String>> result) {
     if (!setPendingOptionsAndResult(options, null, result)) {
       finishWithAlreadyActiveError(result);
       return;
     }
 
-    launchMultiPickImageFromGalleryIntent(usePhotoPicker);
+    launchMultiPickImageFromGalleryIntent(usePhotoPicker, limit);
   }
 
   private void launchPickImageFromGalleryIntent(Boolean usePhotoPicker) {
@@ -452,11 +455,11 @@
     activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY);
   }
 
-  private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker) {
+  private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker, int limit) {
     Intent pickMultiImageIntent;
     if (usePhotoPicker) {
       pickMultiImageIntent =
-          new ActivityResultContracts.PickMultipleVisualMedia()
+          new ActivityResultContracts.PickMultipleVisualMedia(limit)
               .createIntent(
                   activity,
                   new PickVisualMediaRequest.Builder()
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
index b5deb28..8096930 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
@@ -125,7 +125,7 @@
       this.messenger = messenger;
 
       delegate = constructDelegate(activity);
-      ImagePickerApi.setup(messenger, handler);
+      ImagePickerApi.setUp(messenger, handler);
       observer = new LifeCycleObserver(activity);
       if (registrar != null) {
         // V1 embedding setup for activity listeners.
@@ -159,7 +159,7 @@
         lifecycle = null;
       }
 
-      ImagePickerApi.setup(messenger, null);
+      ImagePickerApi.setUp(messenger, null);
 
       if (application != null) {
         application.unregisterActivityLifecycleCallbacks(observer);
@@ -317,7 +317,10 @@
 
     setCameraDevice(delegate, source);
     if (generalOptions.getAllowMultiple()) {
-      delegate.chooseMultiImageFromGallery(options, generalOptions.getUsePhotoPicker(), result);
+      int limit = ImagePickerUtils.getLimitFromOption(generalOptions);
+
+      delegate.chooseMultiImageFromGallery(
+          options, generalOptions.getUsePhotoPicker(), limit, result);
     } else {
       switch (source.getType()) {
         case GALLERY:
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
index 6a93c69f..d140564 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
@@ -5,10 +5,13 @@
 package io.flutter.plugins.imagepicker;
 
 import android.Manifest;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.provider.MediaStore;
+import androidx.activity.result.contract.ActivityResultContracts;
 import java.util.Arrays;
 
 final class ImagePickerUtils {
@@ -54,4 +57,32 @@
     boolean greatOrEqualM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
     return greatOrEqualM && isPermissionPresentInManifest(context, Manifest.permission.CAMERA);
   }
+
+  /**
+   * The system photo picker has a maximum limit of selectable items returned by
+   * [MediaStore.getPickImagesMaxLimit()] On devices supporting picker provided via
+   * [ACTION_SYSTEM_FALLBACK_PICK_IMAGES], the limit may be ignored if it's higher than the allowed
+   * limit. On devices not supporting the photo picker, the limit is ignored.
+   *
+   * @see MediaStore.EXTRA_PICK_IMAGES_MAX
+   */
+  @SuppressLint({"NewApi", "ClassVerificationFailure"})
+  static int getMaxItems() {
+    if (ActivityResultContracts.PickVisualMedia.isSystemPickerAvailable$activity_release()) {
+      return MediaStore.getPickImagesMaxLimit();
+    } else {
+      return Integer.MAX_VALUE;
+    }
+  }
+
+  static int getLimitFromOption(Messages.GeneralOptions generalOptions) {
+    Long limit = generalOptions.getLimit();
+    int effectiveLimit = getMaxItems();
+
+    if (limit != null && limit < effectiveLimit) {
+      effectiveLimit = Math.toIntExact(limit);
+    }
+
+    return effectiveLimit;
+  }
 }
diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
index 8a19cfd..b987a22 100644
--- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
+++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
@@ -1,11 +1,14 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 package io.flutter.plugins.imagepicker;
 
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
 import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -14,6 +17,8 @@
 import io.flutter.plugin.common.MessageCodec;
 import io.flutter.plugin.common.StandardMessageCodec;
 import java.io.ByteArrayOutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,6 +60,10 @@
     return errorList;
   }
 
+  @Target(METHOD)
+  @Retention(CLASS)
+  @interface CanIgnoreReturnValue {}
+
   public enum SourceCamera {
     REAR(0),
     FRONT(1);
@@ -116,6 +125,16 @@
       this.usePhotoPicker = setterArg;
     }
 
+    private @Nullable Long limit;
+
+    public @Nullable Long getLimit() {
+      return limit;
+    }
+
+    public void setLimit(@Nullable Long setterArg) {
+      this.limit = setterArg;
+    }
+
     /** Constructor is non-public to enforce null safety; use Builder. */
     GeneralOptions() {}
 
@@ -123,6 +142,7 @@
 
       private @Nullable Boolean allowMultiple;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setAllowMultiple(@NonNull Boolean setterArg) {
         this.allowMultiple = setterArg;
         return this;
@@ -130,24 +150,35 @@
 
       private @Nullable Boolean usePhotoPicker;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setUsePhotoPicker(@NonNull Boolean setterArg) {
         this.usePhotoPicker = setterArg;
         return this;
       }
 
+      private @Nullable Long limit;
+
+      @CanIgnoreReturnValue
+      public @NonNull Builder setLimit(@Nullable Long setterArg) {
+        this.limit = setterArg;
+        return this;
+      }
+
       public @NonNull GeneralOptions build() {
         GeneralOptions pigeonReturn = new GeneralOptions();
         pigeonReturn.setAllowMultiple(allowMultiple);
         pigeonReturn.setUsePhotoPicker(usePhotoPicker);
+        pigeonReturn.setLimit(limit);
         return pigeonReturn;
       }
     }
 
     @NonNull
     ArrayList<Object> toList() {
-      ArrayList<Object> toListResult = new ArrayList<Object>(2);
+      ArrayList<Object> toListResult = new ArrayList<Object>(3);
       toListResult.add(allowMultiple);
       toListResult.add(usePhotoPicker);
+      toListResult.add(limit);
       return toListResult;
     }
 
@@ -157,6 +188,9 @@
       pigeonResult.setAllowMultiple((Boolean) allowMultiple);
       Object usePhotoPicker = list.get(1);
       pigeonResult.setUsePhotoPicker((Boolean) usePhotoPicker);
+      Object limit = list.get(2);
+      pigeonResult.setLimit(
+          (limit == null) ? null : ((limit instanceof Integer) ? (Integer) limit : (Long) limit));
       return pigeonResult;
     }
   }
@@ -214,6 +248,7 @@
 
       private @Nullable Double maxWidth;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setMaxWidth(@Nullable Double setterArg) {
         this.maxWidth = setterArg;
         return this;
@@ -221,6 +256,7 @@
 
       private @Nullable Double maxHeight;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setMaxHeight(@Nullable Double setterArg) {
         this.maxHeight = setterArg;
         return this;
@@ -228,6 +264,7 @@
 
       private @Nullable Long quality;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setQuality(@NonNull Long setterArg) {
         this.quality = setterArg;
         return this;
@@ -288,6 +325,7 @@
 
       private @Nullable ImageSelectionOptions imageSelectionOptions;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setImageSelectionOptions(@NonNull ImageSelectionOptions setterArg) {
         this.imageSelectionOptions = setterArg;
         return this;
@@ -339,6 +377,7 @@
 
       private @Nullable Long maxDurationSeconds;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setMaxDurationSeconds(@Nullable Long setterArg) {
         this.maxDurationSeconds = setterArg;
         return this;
@@ -407,6 +446,7 @@
 
       private @Nullable SourceType type;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setType(@NonNull SourceType setterArg) {
         this.type = setterArg;
         return this;
@@ -414,6 +454,7 @@
 
       private @Nullable SourceCamera camera;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setCamera(@Nullable SourceCamera setterArg) {
         this.camera = setterArg;
         return this;
@@ -438,7 +479,7 @@
     static @NonNull SourceSpecification fromList(@NonNull ArrayList<Object> list) {
       SourceSpecification pigeonResult = new SourceSpecification();
       Object type = list.get(0);
-      pigeonResult.setType(type == null ? null : SourceType.values()[(int) type]);
+      pigeonResult.setType(SourceType.values()[(int) type]);
       Object camera = list.get(1);
       pigeonResult.setCamera(camera == null ? null : SourceCamera.values()[(int) camera]);
       return pigeonResult;
@@ -483,6 +524,7 @@
 
       private @Nullable String code;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setCode(@NonNull String setterArg) {
         this.code = setterArg;
         return this;
@@ -490,6 +532,7 @@
 
       private @Nullable String message;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setMessage(@Nullable String setterArg) {
         this.message = setterArg;
         return this;
@@ -578,6 +621,7 @@
 
       private @Nullable CacheRetrievalType type;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setType(@NonNull CacheRetrievalType setterArg) {
         this.type = setterArg;
         return this;
@@ -585,6 +629,7 @@
 
       private @Nullable CacheRetrievalError error;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setError(@Nullable CacheRetrievalError setterArg) {
         this.error = setterArg;
         return this;
@@ -592,6 +637,7 @@
 
       private @Nullable List<String> paths;
 
+      @CanIgnoreReturnValue
       public @NonNull Builder setPaths(@NonNull List<String> setterArg) {
         this.paths = setterArg;
         return this;
@@ -618,7 +664,7 @@
     static @NonNull CacheRetrievalResult fromList(@NonNull ArrayList<Object> list) {
       CacheRetrievalResult pigeonResult = new CacheRetrievalResult();
       Object type = list.get(0);
-      pigeonResult.setType(type == null ? null : CacheRetrievalType.values()[(int) type]);
+      pigeonResult.setType(CacheRetrievalType.values()[(int) type]);
       Object error = list.get(1);
       pigeonResult.setError(
           (error == null) ? null : CacheRetrievalError.fromList((ArrayList<Object>) error));
@@ -628,10 +674,28 @@
     }
   }
 
+  /** Asynchronous error handling return type for non-nullable API method returns. */
   public interface Result<T> {
-    @SuppressWarnings("UnknownNullness")
-    void success(T result);
+    /** Success case callback method for handling returns. */
+    void success(@NonNull T result);
 
+    /** Failure case callback method for handling errors. */
+    void error(@NonNull Throwable error);
+  }
+  /** Asynchronous error handling return type for nullable API method returns. */
+  public interface NullableResult<T> {
+    /** Success case callback method for handling returns. */
+    void success(@Nullable T result);
+
+    /** Failure case callback method for handling errors. */
+    void error(@NonNull Throwable error);
+  }
+  /** Asynchronous error handling return type for void API method returns. */
+  public interface VoidResult {
+    /** Success case callback method for handling returns. */
+    void success();
+
+    /** Failure case callback method for handling errors. */
     void error(@NonNull Throwable error);
   }
 
@@ -734,13 +798,13 @@
       return ImagePickerApiCodec.INSTANCE;
     }
     /** Sets up an instance of `ImagePickerApi` to handle messages through the `binaryMessenger`. */
-    static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ImagePickerApi api) {
+    static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable ImagePickerApi api) {
       {
         BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
                 binaryMessenger,
-                "dev.flutter.pigeon.ImagePickerApi.pickImages",
+                "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages",
                 getCodec(),
                 taskQueue);
         if (api != null) {
@@ -775,7 +839,7 @@
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
                 binaryMessenger,
-                "dev.flutter.pigeon.ImagePickerApi.pickVideos",
+                "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos",
                 getCodec(),
                 taskQueue);
         if (api != null) {
@@ -808,7 +872,9 @@
       {
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
-                binaryMessenger, "dev.flutter.pigeon.ImagePickerApi.pickMedia", getCodec());
+                binaryMessenger,
+                "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia",
+                getCodec());
         if (api != null) {
           channel.setMessageHandler(
               (message, reply) -> {
@@ -841,7 +907,7 @@
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
                 binaryMessenger,
-                "dev.flutter.pigeon.ImagePickerApi.retrieveLostResults",
+                "dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults",
                 getCodec(),
                 taskQueue);
         if (api != null) {
diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
index 92c988c..81546da 100644
--- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
+++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java
@@ -160,7 +160,8 @@
     ImagePickerDelegate delegate =
         createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null);
 
-    delegate.chooseMultiImageFromGallery(DEFAULT_IMAGE_OPTIONS, false, mockResult);
+    delegate.chooseMultiImageFromGallery(
+        DEFAULT_IMAGE_OPTIONS, false, Integer.MAX_VALUE, mockResult);
 
     verifyFinishedWithAlreadyActiveError();
     verifyNoMoreInteractions(mockResult);
@@ -207,7 +208,8 @@
   public void chooseMultiImageFromGallery_launchesChooseFromGalleryIntent() {
 
     ImagePickerDelegate delegate = createDelegate();
-    delegate.chooseMultiImageFromGallery(DEFAULT_IMAGE_OPTIONS, true, mockResult);
+    delegate.chooseMultiImageFromGallery(
+        DEFAULT_IMAGE_OPTIONS, true, Integer.MAX_VALUE, mockResult);
 
     verify(mockActivity)
         .startActivityForResult(
@@ -220,7 +222,8 @@
   public void chooseMultiImageFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() {
 
     ImagePickerDelegate delegate = createDelegate();
-    delegate.chooseMultiImageFromGallery(DEFAULT_IMAGE_OPTIONS, false, mockResult);
+    delegate.chooseMultiImageFromGallery(
+        DEFAULT_IMAGE_OPTIONS, false, Integer.MAX_VALUE, mockResult);
 
     verify(mockActivity)
         .startActivityForResult(
diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
index b2c281c..cf088b5 100644
--- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
+++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java
@@ -52,6 +52,19 @@
       new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(false).build();
   private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER =
       new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(true).build();
+  private static final GeneralOptions
+      GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT =
+          new GeneralOptions.Builder()
+              .setUsePhotoPicker(false)
+              .setAllowMultiple(true)
+              .setLimit((long) 5)
+              .build();
+  private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER_WITH_LIMIT =
+      new GeneralOptions.Builder()
+          .setUsePhotoPicker(true)
+          .setAllowMultiple(true)
+          .setLimit((long) 5)
+          .build();
   private static final SourceSpecification SOURCE_GALLERY =
       new SourceSpecification.Builder().setType(Messages.SourceType.GALLERY).build();
   private static final SourceSpecification SOURCE_CAMERA_FRONT =
@@ -171,7 +184,8 @@
         DEFAULT_IMAGE_OPTIONS,
         GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
         mockResult);
-    verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(false), any());
+    verify(mockImagePickerDelegate)
+        .chooseMultiImageFromGallery(any(), eq(false), eq(Integer.MAX_VALUE), any());
     verifyNoInteractions(mockResult);
   }
 
@@ -182,7 +196,30 @@
         DEFAULT_IMAGE_OPTIONS,
         GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER,
         mockResult);
-    verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(true), any());
+    verify(mockImagePickerDelegate)
+        .chooseMultiImageFromGallery(any(), eq(true), eq(Integer.MAX_VALUE), any());
+    verifyNoInteractions(mockResult);
+  }
+
+  @Test
+  public void pickImages_usingPhotoPicker_withLimit5_invokesChooseMultiImageFromGallery() {
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER_WITH_LIMIT,
+        mockResult);
+    verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(true), eq(5), any());
+    verifyNoInteractions(mockResult);
+  }
+
+  @Test
+  public void pickImages_withLimit5_invokesChooseMultiImageFromGallery() {
+    plugin.pickImages(
+        SOURCE_GALLERY,
+        DEFAULT_IMAGE_OPTIONS,
+        GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT,
+        mockResult);
+    verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(false), eq(5), any());
     verifyNoInteractions(mockResult);
   }
 
diff --git a/packages/image_picker/image_picker_android/example/lib/main.dart b/packages/image_picker/image_picker_android/example/lib/main.dart
index 94cbd7d..8218f2d 100755
--- a/packages/image_picker/image_picker_android/example/lib/main.dart
+++ b/packages/image_picker/image_picker_android/example/lib/main.dart
@@ -74,6 +74,7 @@
   final TextEditingController maxWidthController = TextEditingController();
   final TextEditingController maxHeightController = TextEditingController();
   final TextEditingController qualityController = TextEditingController();
+  final TextEditingController limitController = TextEditingController();
 
   Future<void> _playVideo(XFile? file) async {
     if (file != null && mounted) {
@@ -109,8 +110,8 @@
         }
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, true, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final ImageOptions imageOptions = ImageOptions(
               maxWidth: maxWidth,
@@ -120,12 +121,15 @@
             final List<XFile> pickedFileList = isMedia
                 ? await _picker.getMedia(
                     options: MediaOptions(
-                        allowMultiple: isMultiImage,
-                        imageOptions: imageOptions),
+                      allowMultiple: isMultiImage,
+                      imageOptions: imageOptions,
+                      limit: limit,
+                    ),
                   )
                 : await _picker.getMultiImageWithOptions(
                     options: MultiImagePickerOptions(
                       imageOptions: imageOptions,
+                      limit: limit,
                     ),
                   );
             if (pickedFileList.isNotEmpty && context.mounted) {
@@ -141,8 +145,8 @@
           }
         });
       } else if (isMedia) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final List<XFile> pickedFileList = <XFile>[];
             final XFile? media = _firstOrNull(await _picker.getMedia(
@@ -166,8 +170,8 @@
           }
         });
       } else {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final XFile? pickedFile = await _picker.getImageFromSource(
               source: source,
@@ -481,7 +485,7 @@
   }
 
   Future<void> _displayPickImageDialog(
-      BuildContext context, OnPickImageCallback onPick) async {
+      BuildContext context, bool isMulti, OnPickImageCallback onPick) async {
     return showDialog(
         context: context,
         builder: (BuildContext context) {
@@ -509,6 +513,13 @@
                   decoration: const InputDecoration(
                       hintText: 'Enter quality if desired'),
                 ),
+                if (isMulti)
+                  TextField(
+                    controller: limitController,
+                    keyboardType: TextInputType.number,
+                    decoration: const InputDecoration(
+                        hintText: 'Enter limit if desired'),
+                  ),
               ],
             ),
             actions: <Widget>[
@@ -530,7 +541,10 @@
                     final int? quality = qualityController.text.isNotEmpty
                         ? int.parse(qualityController.text)
                         : null;
-                    onPick(width, height, quality);
+                    final int? limit = limitController.text.isNotEmpty
+                        ? int.parse(limitController.text)
+                        : null;
+                    onPick(width, height, quality, limit);
                     Navigator.of(context).pop();
                   }),
             ],
@@ -547,7 +561,7 @@
 }
 
 typedef OnPickImageCallback = void Function(
-    double? maxWidth, double? maxHeight, int? quality);
+    double? maxWidth, double? maxHeight, int? quality, int? limit);
 
 class AspectRatioVideo extends StatefulWidget {
   const AspectRatioVideo(this.controller, {super.key});
diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml
index c2fa9e9..500b47c 100644
--- a/packages/image_picker/image_picker_android/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/example/pubspec.yaml
@@ -3,8 +3,8 @@
 publish_to: none
 
 environment:
-  sdk: ^3.1.0
-  flutter: ">=3.13.0"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 dependencies:
   flutter:
@@ -19,7 +19,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
   mime: ^1.0.4
   video_player: ^2.1.4
 
diff --git a/packages/image_picker/image_picker_android/lib/image_picker_android.dart b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
index c9e2c87..2bcebe2 100644
--- a/packages/image_picker/image_picker_android/lib/image_picker_android.dart
+++ b/packages/image_picker/image_picker_android/lib/image_picker_android.dart
@@ -67,6 +67,7 @@
     double? maxWidth,
     double? maxHeight,
     int? imageQuality,
+    int? limit,
   }) {
     if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
       throw ArgumentError.value(
@@ -81,6 +82,10 @@
       throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
     }
 
+    if (limit != null && limit < 2) {
+      throw ArgumentError.value(limit, 'limit', 'cannot be lower than 2');
+    }
+
     return _hostApi.pickImages(
       SourceSpecification(type: SourceType.gallery),
       ImageSelectionOptions(
@@ -88,7 +93,10 @@
           maxHeight: maxHeight,
           quality: imageQuality ?? 100),
       GeneralOptions(
-          allowMultiple: true, usePhotoPicker: useAndroidPhotoPicker),
+        allowMultiple: true,
+        usePhotoPicker: useAndroidPhotoPicker,
+        limit: limit,
+      ),
     );
   }
 
@@ -210,15 +218,30 @@
   }
 
   @override
+  Future<List<XFile>> getMultiImageWithOptions({
+    MultiImagePickerOptions options = const MultiImagePickerOptions(),
+  }) async {
+    final List<dynamic> paths = await _getMultiImagePath(
+      maxWidth: options.imageOptions.maxWidth,
+      maxHeight: options.imageOptions.maxHeight,
+      imageQuality: options.imageOptions.imageQuality,
+      limit: options.limit,
+    );
+
+    if (paths.isEmpty) {
+      return <XFile>[];
+    }
+
+    return paths.map((dynamic path) => XFile(path as String)).toList();
+  }
+
+  @override
   Future<List<XFile>> getMedia({
     required MediaOptions options,
   }) async {
     return (await _hostApi.pickMedia(
       _mediaOptionsToMediaSelectionOptions(options),
-      GeneralOptions(
-        allowMultiple: options.allowMultiple,
-        usePhotoPicker: useAndroidPhotoPicker,
-      ),
+      _mediaOptionsToGeneralOptions(options),
     ))
         .map((String? path) => XFile(path!))
         .toList();
@@ -243,6 +266,7 @@
     final ImageSelectionOptions imageSelectionOptions =
         _imageOptionsToImageSelectionOptionsWithValidator(
             mediaOptions.imageOptions);
+
     return MediaSelectionOptions(
       imageSelectionOptions: imageSelectionOptions,
     );
@@ -270,6 +294,29 @@
         quality: imageQuality ?? 100, maxHeight: maxHeight, maxWidth: maxWidth);
   }
 
+  GeneralOptions _mediaOptionsToGeneralOptions(MediaOptions options) {
+    final bool allowMultiple = options.allowMultiple;
+    final int? limit = options.limit;
+
+    if (!allowMultiple && limit != null) {
+      throw ArgumentError.value(
+        allowMultiple,
+        'allowMultiple',
+        'cannot be false, when limit is not null',
+      );
+    }
+
+    if (limit != null && limit < 2) {
+      throw ArgumentError.value(limit, 'limit', 'cannot be lower then 2');
+    }
+
+    return GeneralOptions(
+      allowMultiple: allowMultiple,
+      usePhotoPicker: useAndroidPhotoPicker,
+      limit: limit,
+    );
+  }
+
   @override
   Future<LostData> retrieveLostData() async {
     final LostDataResponse result = await getLostData();
diff --git a/packages/image_picker/image_picker_android/lib/src/messages.g.dart b/packages/image_picker/image_picker_android/lib/src/messages.g.dart
index 476e80d..1466b15 100644
--- a/packages/image_picker/image_picker_android/lib/src/messages.g.dart
+++ b/packages/image_picker/image_picker_android/lib/src/messages.g.dart
@@ -1,9 +1,9 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
 
 import 'dart:async';
 import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
@@ -11,6 +11,24 @@
 import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
 import 'package:flutter/services.dart';
 
+PlatformException _createConnectionError(String channelName) {
+  return PlatformException(
+    code: 'channel-error',
+    message: 'Unable to establish connection on channel: "$channelName".',
+  );
+}
+
+List<Object?> wrapResponse(
+    {Object? result, PlatformException? error, bool empty = false}) {
+  if (empty) {
+    return <Object?>[];
+  }
+  if (error == null) {
+    return <Object?>[result];
+  }
+  return <Object?>[error.code, error.message, error.details];
+}
+
 enum SourceCamera {
   rear,
   front,
@@ -30,16 +48,20 @@
   GeneralOptions({
     required this.allowMultiple,
     required this.usePhotoPicker,
+    this.limit,
   });
 
   bool allowMultiple;
 
   bool usePhotoPicker;
 
+  int? limit;
+
   Object encode() {
     return <Object?>[
       allowMultiple,
       usePhotoPicker,
+      limit,
     ];
   }
 
@@ -48,6 +70,7 @@
     return GeneralOptions(
       allowMultiple: result[0]! as bool,
       usePhotoPicker: result[1]! as bool,
+      limit: result[2] as int?,
     );
   }
 }
@@ -195,7 +218,7 @@
   CacheRetrievalResult({
     required this.type,
     this.error,
-    required this.paths,
+    this.paths = const <String>[],
   });
 
   /// The type of the retrieved data.
@@ -288,43 +311,43 @@
   /// available for dependency injection.  If it is left null, the default
   /// BinaryMessenger will be used which routes to the host platform.
   ImagePickerApi({BinaryMessenger? binaryMessenger})
-      : _binaryMessenger = binaryMessenger;
-  final BinaryMessenger? _binaryMessenger;
+      : __pigeon_binaryMessenger = binaryMessenger;
+  final BinaryMessenger? __pigeon_binaryMessenger;
 
-  static const MessageCodec<Object?> codec = _ImagePickerApiCodec();
+  static const MessageCodec<Object?> pigeonChannelCodec =
+      _ImagePickerApiCodec();
 
   /// Selects images and returns their paths.
   ///
   /// Elements must not be null, by convention. See
   /// https://github.com/flutter/flutter/issues/97848
-  Future<List<String?>> pickImages(
-      SourceSpecification arg_source,
-      ImageSelectionOptions arg_options,
-      GeneralOptions arg_generalOptions) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.ImagePickerApi.pickImages', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel
-            .send(<Object?>[arg_source, arg_options, arg_generalOptions])
-        as List<Object?>?;
-    if (replyList == null) {
+  Future<List<String?>> pickImages(SourceSpecification source,
+      ImageSelectionOptions options, GeneralOptions generalOptions) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+        .send(<Object?>[source, options, generalOptions]) as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
-      );
-    } else if (replyList[0] == null) {
+    } else if (__pigeon_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as List<Object?>?)!.cast<String?>();
+      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
     }
   }
 
@@ -332,34 +355,33 @@
   ///
   /// Elements must not be null, by convention. See
   /// https://github.com/flutter/flutter/issues/97848
-  Future<List<String?>> pickVideos(
-      SourceSpecification arg_source,
-      VideoSelectionOptions arg_options,
-      GeneralOptions arg_generalOptions) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.ImagePickerApi.pickVideos', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel
-            .send(<Object?>[arg_source, arg_options, arg_generalOptions])
-        as List<Object?>?;
-    if (replyList == null) {
+  Future<List<String?>> pickVideos(SourceSpecification source,
+      VideoSelectionOptions options, GeneralOptions generalOptions) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+        .send(<Object?>[source, options, generalOptions]) as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
-      );
-    } else if (replyList[0] == null) {
+    } else if (__pigeon_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as List<Object?>?)!.cast<String?>();
+      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
     }
   }
 
@@ -367,55 +389,59 @@
   ///
   /// Elements must not be null, by convention. See
   /// https://github.com/flutter/flutter/issues/97848
-  Future<List<String?>> pickMedia(
-      MediaSelectionOptions arg_mediaSelectionOptions,
-      GeneralOptions arg_generalOptions) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel
-            .send(<Object?>[arg_mediaSelectionOptions, arg_generalOptions])
+  Future<List<String?>> pickMedia(MediaSelectionOptions mediaSelectionOptions,
+      GeneralOptions generalOptions) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+            .send(<Object?>[mediaSelectionOptions, generalOptions])
         as List<Object?>?;
-    if (replyList == null) {
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
-      );
-    } else if (replyList[0] == null) {
+    } else if (__pigeon_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as List<Object?>?)!.cast<String?>();
+      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
     }
   }
 
   /// Returns results from a previous app session, if any.
   Future<CacheRetrievalResult?> retrieveLostResults() async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.ImagePickerApi.retrieveLostResults', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
-    if (replyList == null) {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList =
+        await __pigeon_channel.send(null) as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
-      );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
     } else {
-      return (replyList[0] as CacheRetrievalResult?);
+      return (__pigeon_replyList[0] as CacheRetrievalResult?);
     }
   }
 }
diff --git a/packages/image_picker/image_picker_android/pigeons/messages.dart b/packages/image_picker/image_picker_android/pigeons/messages.dart
index 9d264b5..7f39ae5 100644
--- a/packages/image_picker/image_picker_android/pigeons/messages.dart
+++ b/packages/image_picker/image_picker_android/pigeons/messages.dart
@@ -14,9 +14,10 @@
   copyrightHeader: 'pigeons/copyright.txt',
 ))
 class GeneralOptions {
-  GeneralOptions(this.allowMultiple, this.usePhotoPicker);
+  GeneralOptions(this.allowMultiple, this.usePhotoPicker, this.limit);
   bool allowMultiple;
   bool usePhotoPicker;
+  int? limit;
 }
 
 /// Options for image selection and output.
diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml
index 7a98a4f..8f0606a 100755
--- a/packages/image_picker/image_picker_android/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/pubspec.yaml
@@ -2,11 +2,11 @@
 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.9+6
+version: 0.8.10
 
 environment:
-  sdk: ^3.2.0
-  flutter: ">=3.16.0"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 flutter:
   plugin:
@@ -21,13 +21,13 @@
   flutter:
     sdk: flutter
   flutter_plugin_android_lifecycle: ^2.0.1
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
   mockito: 5.4.4
-  pigeon: ^9.2.5
+  pigeon: ^17.0.0
 
 topics:
   - camera
diff --git a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
index 0b0cab4..172b135 100644
--- a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
+++ b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart
@@ -156,6 +156,7 @@
       expect(api.passedImageOptions?.maxWidth, null);
       expect(api.passedImageOptions?.maxHeight, null);
       expect(api.passedImageOptions?.quality, 100);
+      expect(api.limit, null);
     });
 
     test('passes image option arguments correctly', () async {
@@ -465,6 +466,7 @@
       expect(api.passedImageOptions?.maxWidth, null);
       expect(api.passedImageOptions?.maxHeight, null);
       expect(api.passedImageOptions?.quality, 100);
+      expect(api.limit, null);
     });
 
     test('passes image option arguments correctly', () async {
@@ -681,6 +683,7 @@
       expect(api.passedImageOptions?.maxWidth, null);
       expect(api.passedImageOptions?.maxHeight, null);
       expect(api.passedImageOptions?.quality, 100);
+      expect(api.limit, null);
     });
 
     test('passes image option arguments correctly', () async {
@@ -692,11 +695,13 @@
           maxHeight: 20.0,
           imageQuality: 70,
         ),
+        limit: 5,
       ));
 
       expect(api.passedImageOptions?.maxWidth, 10.0);
       expect(api.passedImageOptions?.maxHeight, 20.0);
       expect(api.passedImageOptions?.quality, 70);
+      expect(api.limit, 5);
     });
 
     test('does not accept a negative width or height argument', () {
@@ -743,6 +748,37 @@
       );
     });
 
+    test('does not accept an invalid limit argument', () {
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            limit: -1,
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(
+            allowMultiple: true,
+            limit: 0,
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a not null limit when allowMultiple is false', () {
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(allowMultiple: false, limit: 5),
+        ),
+        throwsArgumentError,
+      );
+    });
+
     test('handles an empty path response gracefully', () async {
       api.returnValue = <String>[];
 
@@ -926,6 +962,7 @@
   VideoSelectionOptions? passedVideoOptions;
   bool? passedAllowMultiple;
   bool? passedPhotoPickerFlag;
+  int? limit;
   _LastPickType? lastCall;
 
   @override
@@ -939,6 +976,7 @@
     passedImageOptions = options;
     passedAllowMultiple = generalOptions.allowMultiple;
     passedPhotoPickerFlag = generalOptions.usePhotoPicker;
+    limit = generalOptions.limit;
     return returnValue as List<String?>? ?? <String>[];
   }
 
@@ -951,6 +989,7 @@
     passedImageOptions = options.imageSelectionOptions;
     passedPhotoPickerFlag = generalOptions.usePhotoPicker;
     passedAllowMultiple = generalOptions.allowMultiple;
+    limit = generalOptions.limit;
     return returnValue as List<String?>? ?? <String>[];
   }
 
diff --git a/packages/image_picker/image_picker_android/test/test_api.g.dart b/packages/image_picker/image_picker_android/test/test_api.g.dart
index d3b6891..52aefcd 100644
--- a/packages/image_picker/image_picker_android/test/test_api.g.dart
+++ b/packages/image_picker/image_picker_android/test/test_api.g.dart
@@ -1,9 +1,9 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers
 // ignore_for_file: avoid_relative_lib_imports
 import 'dart:async';
 import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
@@ -69,7 +69,8 @@
 abstract class TestHostImagePickerApi {
   static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
       TestDefaultBinaryMessengerBinding.instance;
-  static const MessageCodec<Object?> codec = _TestHostImagePickerApiCodec();
+  static const MessageCodec<Object?> pigeonChannelCodec =
+      _TestHostImagePickerApiCodec();
 
   /// Selects images and returns their paths.
   ///
@@ -98,111 +99,146 @@
   static void setup(TestHostImagePickerApi? api,
       {BinaryMessenger? binaryMessenger}) {
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.ImagePickerApi.pickImages', codec,
+      final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages',
+          pigeonChannelCodec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages was null.');
           final List<Object?> args = (message as List<Object?>?)!;
           final SourceSpecification? arg_source =
               (args[0] as SourceSpecification?);
           assert(arg_source != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null SourceSpecification.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages was null, expected non-null SourceSpecification.');
           final ImageSelectionOptions? arg_options =
               (args[1] as ImageSelectionOptions?);
           assert(arg_options != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null ImageSelectionOptions.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages was null, expected non-null ImageSelectionOptions.');
           final GeneralOptions? arg_generalOptions =
               (args[2] as GeneralOptions?);
           assert(arg_generalOptions != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickImages was null, expected non-null GeneralOptions.');
-          final List<String?> output = await api.pickImages(
-              arg_source!, arg_options!, arg_generalOptions!);
-          return <Object?>[output];
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages was null, expected non-null GeneralOptions.');
+          try {
+            final List<String?> output = await api.pickImages(
+                arg_source!, arg_options!, arg_generalOptions!);
+            return <Object?>[output];
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
         });
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.ImagePickerApi.pickVideos', codec,
+      final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos',
+          pigeonChannelCodec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos was null.');
           final List<Object?> args = (message as List<Object?>?)!;
           final SourceSpecification? arg_source =
               (args[0] as SourceSpecification?);
           assert(arg_source != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null SourceSpecification.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos was null, expected non-null SourceSpecification.');
           final VideoSelectionOptions? arg_options =
               (args[1] as VideoSelectionOptions?);
           assert(arg_options != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null VideoSelectionOptions.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos was null, expected non-null VideoSelectionOptions.');
           final GeneralOptions? arg_generalOptions =
               (args[2] as GeneralOptions?);
           assert(arg_generalOptions != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideos was null, expected non-null GeneralOptions.');
-          final List<String?> output = await api.pickVideos(
-              arg_source!, arg_options!, arg_generalOptions!);
-          return <Object?>[output];
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos was null, expected non-null GeneralOptions.');
+          try {
+            final List<String?> output = await api.pickVideos(
+                arg_source!, arg_options!, arg_generalOptions!);
+            return <Object?>[output];
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
         });
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec,
+      final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia',
+          pigeonChannelCodec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia was null.');
           final List<Object?> args = (message as List<Object?>?)!;
           final MediaSelectionOptions? arg_mediaSelectionOptions =
               (args[0] as MediaSelectionOptions?);
           assert(arg_mediaSelectionOptions != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.');
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia was null, expected non-null MediaSelectionOptions.');
           final GeneralOptions? arg_generalOptions =
               (args[1] as GeneralOptions?);
           assert(arg_generalOptions != null,
-              'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null GeneralOptions.');
-          final List<String?> output = await api.pickMedia(
-              arg_mediaSelectionOptions!, arg_generalOptions!);
-          return <Object?>[output];
+              'Argument for dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia was null, expected non-null GeneralOptions.');
+          try {
+            final List<String?> output = await api.pickMedia(
+                arg_mediaSelectionOptions!, arg_generalOptions!);
+            return <Object?>[output];
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
         });
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.ImagePickerApi.retrieveLostResults', codec,
+      final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults',
+          pigeonChannelCodec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
-          // ignore message
-          final CacheRetrievalResult? output = api.retrieveLostResults();
-          return <Object?>[output];
+          try {
+            final CacheRetrievalResult? output = api.retrieveLostResults();
+            return <Object?>[output];
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
         });
       }
     }
diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md
index 566aab0..787524c 100644
--- a/packages/image_picker/image_picker_ios/CHANGELOG.md
+++ b/packages/image_picker/image_picker_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.10
+
+* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected.
+
 ## 0.8.9+2
 
 * Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6.
diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
index 36ca8ec..d54d5c3 100644
--- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
+++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
@@ -175,6 +175,7 @@
   [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
                             quality:@(50)
                        fullMetadata:YES
+                              limit:nil
                          completion:^(NSArray<NSString *> *_Nullable result,
                                       FlutterError *_Nullable error){
                          }];
@@ -198,7 +199,8 @@
       [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
                                    imageQuality:@(50)
                             requestFullMetadata:YES
-                                  allowMultiple:YES];
+                                  allowMultiple:YES
+                                          limit:nil];
 
   [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
                                   completion:^(NSArray<NSString *> *_Nullable result,
@@ -236,6 +238,7 @@
   [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
                             quality:nil
                        fullMetadata:NO
+                              limit:nil
                          completion:^(NSArray<NSString *> *_Nullable result,
                                       FlutterError *_Nullable error){
                          }];
@@ -254,7 +257,8 @@
       [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
                                    imageQuality:@(50)
                             requestFullMetadata:YES
-                                  allowMultiple:YES];
+                                  allowMultiple:YES
+                                          limit:nil];
 
   [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
 
@@ -544,6 +548,7 @@
   [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100]
                             quality:nil
                        fullMetadata:YES
+                              limit:nil
                          completion:^(NSArray<NSString *> *result, FlutterError *error) {
                            XCTAssertNotNil(error);
                            XCTAssertEqualObjects(error.code, @"multiple_request");
@@ -552,6 +557,7 @@
   [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@100 height:@100]
                             quality:nil
                        fullMetadata:YES
+                              limit:nil
                          completion:^(NSArray<NSString *> *result, FlutterError *error){
                          }];
   [self waitForExpectationsWithTimeout:30 handler:nil];
@@ -570,7 +576,8 @@
       [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
                                    imageQuality:@(50)
                             requestFullMetadata:YES
-                                  allowMultiple:YES];
+                                  allowMultiple:YES
+                                          limit:nil];
   XCTestExpectation *firstCallExpectation = [self expectationWithDescription:@"first call"];
   [plugin pickMediaWithMediaSelectionOptions:options
                                   completion:^(NSArray<NSString *> *result, FlutterError *error) {
@@ -608,4 +615,79 @@
   [self waitForExpectationsWithTimeout:30 handler:nil];
 }
 
+- (void)testPickMultiImageWithLimit {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
+                            quality:nil
+                       fullMetadata:NO
+                              limit:@(2)
+                         completion:^(NSArray<NSString *> *_Nullable result,
+                                      FlutterError *_Nullable error){
+                         }];
+  XCTAssertEqual(plugin.callContext.maxImageCount, 2);
+}
+
+- (void)testPickMediaWithLimitAllowsMultiple {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  FLTMediaSelectionOptions *mediaSelectionOptions =
+      [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+                                   imageQuality:nil
+                            requestFullMetadata:NO
+                                  allowMultiple:YES
+                                          limit:@(2)];
+
+  [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
+                                  completion:^(NSArray<NSString *> *_Nullable result,
+                                               FlutterError *_Nullable error){
+                                  }];
+
+  XCTAssertEqual(plugin.callContext.maxImageCount, 2);
+}
+
+- (void)testPickMediaWithLimitMultipleNotAllowed {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  FLTMediaSelectionOptions *mediaSelectionOptions =
+      [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+                                   imageQuality:nil
+                            requestFullMetadata:NO
+                                  allowMultiple:NO
+                                          limit:@(2)];
+
+  [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
+                                  completion:^(NSArray<NSString *> *_Nullable result,
+                                               FlutterError *_Nullable error){
+                                  }];
+
+  XCTAssertEqual(plugin.callContext.maxImageCount, 1);
+}
+
+- (void)testPickMultiImageWithoutLimit {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
+                            quality:nil
+                       fullMetadata:NO
+                              limit:nil
+                         completion:^(NSArray<NSString *> *_Nullable result,
+                                      FlutterError *_Nullable error){
+                         }];
+  XCTAssertEqual(plugin.callContext.maxImageCount, 0);
+}
+
+- (void)testPickMediaWithoutLimitAllowsMultiple {
+  FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
+  FLTMediaSelectionOptions *mediaSelectionOptions =
+      [FLTMediaSelectionOptions makeWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+                                   imageQuality:nil
+                            requestFullMetadata:NO
+                                  allowMultiple:YES
+                                          limit:nil];
+
+  [plugin pickMediaWithMediaSelectionOptions:mediaSelectionOptions
+                                  completion:^(NSArray<NSString *> *_Nullable result,
+                                               FlutterError *_Nullable error){
+                                  }];
+
+  XCTAssertEqual(plugin.callContext.maxImageCount, 0);
+}
+
 @end
diff --git a/packages/image_picker/image_picker_ios/example/lib/main.dart b/packages/image_picker/image_picker_ios/example/lib/main.dart
index 0f42b58..28d0ea2 100755
--- a/packages/image_picker/image_picker_ios/example/lib/main.dart
+++ b/packages/image_picker/image_picker_ios/example/lib/main.dart
@@ -56,6 +56,7 @@
   final TextEditingController maxWidthController = TextEditingController();
   final TextEditingController maxHeightController = TextEditingController();
   final TextEditingController qualityController = TextEditingController();
+  final TextEditingController limitController = TextEditingController();
 
   Future<void> _playVideo(XFile? file) async {
     if (file != null && mounted) {
@@ -88,18 +89,20 @@
             source: source, maxDuration: const Duration(seconds: 10));
         await _playVideo(file);
       } else if (isMultiImage) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, true, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final List<XFile> pickedFileList = isMedia
                 ? await _picker.getMedia(
                     options: MediaOptions(
-                        allowMultiple: isMultiImage,
-                        imageOptions: ImageOptions(
-                          maxWidth: maxWidth,
-                          maxHeight: maxHeight,
-                          imageQuality: quality,
-                        )),
+                      allowMultiple: isMultiImage,
+                      imageOptions: ImageOptions(
+                        maxWidth: maxWidth,
+                        maxHeight: maxHeight,
+                        imageQuality: quality,
+                      ),
+                      limit: limit,
+                    ),
                   )
                 : await _picker.getMultiImageWithOptions(
                     options: MultiImagePickerOptions(
@@ -108,6 +111,7 @@
                         maxHeight: maxHeight,
                         imageQuality: quality,
                       ),
+                      limit: limit,
                     ),
                   );
             setState(() {
@@ -120,18 +124,19 @@
           }
         });
       } else if (isMedia) {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final List<XFile> pickedFileList = <XFile>[];
             final XFile? media = _firstOrNull(await _picker.getMedia(
               options: MediaOptions(
-                  allowMultiple: isMultiImage,
-                  imageOptions: ImageOptions(
-                    maxWidth: maxWidth,
-                    maxHeight: maxHeight,
-                    imageQuality: quality,
-                  )),
+                allowMultiple: isMultiImage,
+                imageOptions: ImageOptions(
+                  maxWidth: maxWidth,
+                  maxHeight: maxHeight,
+                  imageQuality: quality,
+                ),
+              ),
             ));
 
             if (media != null) {
@@ -145,8 +150,8 @@
           }
         });
       } else {
-        await _displayPickImageDialog(context,
-            (double? maxWidth, double? maxHeight, int? quality) async {
+        await _displayPickImageDialog(context, false, (double? maxWidth,
+            double? maxHeight, int? quality, int? limit) async {
           try {
             final XFile? pickedFile = await _picker.getImageFromSource(
               source: source,
@@ -400,7 +405,7 @@
   }
 
   Future<void> _displayPickImageDialog(
-      BuildContext context, OnPickImageCallback onPick) async {
+      BuildContext context, bool isMulti, OnPickImageCallback onPick) async {
     return showDialog(
         context: context,
         builder: (BuildContext context) {
@@ -428,6 +433,13 @@
                   decoration: const InputDecoration(
                       hintText: 'Enter quality if desired'),
                 ),
+                if (isMulti)
+                  TextField(
+                    controller: limitController,
+                    keyboardType: TextInputType.number,
+                    decoration: const InputDecoration(
+                        hintText: 'Enter limit if desired'),
+                  ),
               ],
             ),
             actions: <Widget>[
@@ -449,7 +461,10 @@
                     final int? quality = qualityController.text.isNotEmpty
                         ? int.parse(qualityController.text)
                         : null;
-                    onPick(width, height, quality);
+                    final int? limit = limitController.text.isNotEmpty
+                        ? int.parse(limitController.text)
+                        : null;
+                    onPick(width, height, quality, limit);
                     Navigator.of(context).pop();
                   }),
             ],
@@ -459,7 +474,7 @@
 }
 
 typedef OnPickImageCallback = void Function(
-    double? maxWidth, double? maxHeight, int? quality);
+    double? maxWidth, double? maxHeight, int? quality, int? limit);
 
 class AspectRatioVideo extends StatefulWidget {
   const AspectRatioVideo(this.controller, {super.key});
diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml
index e4dd090..57c9e2d 100755
--- a/packages/image_picker/image_picker_ios/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml
@@ -3,8 +3,8 @@
 publish_to: none
 
 environment:
-  sdk: ^3.2.3
-  flutter: ">=3.16.6"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 dependencies:
   flutter:
@@ -16,7 +16,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
   mime: ^1.0.4
   video_player: ^2.1.4
 
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
index 10081a8..c65542d 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
@@ -190,6 +190,7 @@
 - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize
                           quality:(nullable NSNumber *)imageQuality
                      fullMetadata:(BOOL)fullMetadata
+                            limit:(nullable NSNumber *)limit
                        completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
                                                     FlutterError *_Nullable))completion {
   [self cancelInProgressCall];
@@ -198,6 +199,7 @@
   context.maxSize = maxSize;
   context.imageQuality = imageQuality;
   context.requestFullMetadata = fullMetadata;
+  context.maxImageCount = limit.intValue;
 
   if (@available(iOS 14, *)) {
     [self launchPHPickerWithContext:context];
@@ -219,8 +221,11 @@
   context.imageQuality = [mediaSelectionOptions imageQuality];
   context.requestFullMetadata = [mediaSelectionOptions requestFullMetadata];
   context.includeVideo = YES;
+  NSNumber *limit = [mediaSelectionOptions limit];
   if (!mediaSelectionOptions.allowMultiple) {
     context.maxImageCount = 1;
+  } else if (limit != nil) {
+    context.maxImageCount = limit.intValue;
   }
 
   if (@available(iOS 14, *)) {
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
index 593f882..de9a820 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v13.0.0), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import <Foundation/Foundation.h>
@@ -51,11 +51,13 @@
 + (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize
                    imageQuality:(nullable NSNumber *)imageQuality
             requestFullMetadata:(BOOL)requestFullMetadata
-                  allowMultiple:(BOOL)allowMultiple;
+                  allowMultiple:(BOOL)allowMultiple
+                          limit:(nullable NSNumber *)limit;
 @property(nonatomic, strong) FLTMaxSize *maxSize;
 @property(nonatomic, strong, nullable) NSNumber *imageQuality;
 @property(nonatomic, assign) BOOL requestFullMetadata;
 @property(nonatomic, assign) BOOL allowMultiple;
+@property(nonatomic, strong, nullable) NSNumber *limit;
 @end
 
 @interface FLTSourceSpecification : NSObject
@@ -78,6 +80,7 @@
 - (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize
                           quality:(nullable NSNumber *)imageQuality
                      fullMetadata:(BOOL)requestFullMetadata
+                            limit:(nullable NSNumber *)limit
                        completion:(void (^)(NSArray<NSString *> *_Nullable,
                                             FlutterError *_Nullable))completion;
 - (void)pickVideoWithSource:(FLTSourceSpecification *)source
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
index 1659cb8..a1f5794 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
@@ -1,7 +1,7 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v13.0.0), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import "messages.g.h"
@@ -16,6 +16,20 @@
 #error File requires ARC to be enabled.
 #endif
 
+static NSArray *wrapResult(id result, FlutterError *error) {
+  if (error) {
+    return @[
+      error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null]
+    ];
+  }
+  return @[ result ?: [NSNull null] ];
+}
+
+static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
+  id result = array[key];
+  return (result == [NSNull null]) ? nil : result;
+}
+
 @implementation FLTSourceCameraBox
 - (instancetype)initWithValue:(FLTSourceCamera)value {
   self = [super init];
@@ -36,19 +50,6 @@
 }
 @end
 
-static NSArray *wrapResult(id result, FlutterError *error) {
-  if (error) {
-    return @[
-      error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null]
-    ];
-  }
-  return @[ result ?: [NSNull null] ];
-}
-static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
-  id result = array[key];
-  return (result == [NSNull null]) ? nil : result;
-}
-
 @interface FLTMaxSize ()
 + (FLTMaxSize *)fromList:(NSArray *)list;
 + (nullable FLTMaxSize *)nullableFromList:(NSArray *)list;
@@ -95,12 +96,14 @@
 + (instancetype)makeWithMaxSize:(FLTMaxSize *)maxSize
                    imageQuality:(nullable NSNumber *)imageQuality
             requestFullMetadata:(BOOL)requestFullMetadata
-                  allowMultiple:(BOOL)allowMultiple {
+                  allowMultiple:(BOOL)allowMultiple
+                          limit:(nullable NSNumber *)limit {
   FLTMediaSelectionOptions *pigeonResult = [[FLTMediaSelectionOptions alloc] init];
   pigeonResult.maxSize = maxSize;
   pigeonResult.imageQuality = imageQuality;
   pigeonResult.requestFullMetadata = requestFullMetadata;
   pigeonResult.allowMultiple = allowMultiple;
+  pigeonResult.limit = limit;
   return pigeonResult;
 }
 + (FLTMediaSelectionOptions *)fromList:(NSArray *)list {
@@ -109,6 +112,7 @@
   pigeonResult.imageQuality = GetNullableObjectAtIndex(list, 1);
   pigeonResult.requestFullMetadata = [GetNullableObjectAtIndex(list, 2) boolValue];
   pigeonResult.allowMultiple = [GetNullableObjectAtIndex(list, 3) boolValue];
+  pigeonResult.limit = GetNullableObjectAtIndex(list, 4);
   return pigeonResult;
 }
 + (nullable FLTMediaSelectionOptions *)nullableFromList:(NSArray *)list {
@@ -120,6 +124,7 @@
     self.imageQuality ?: [NSNull null],
     @(self.requestFullMetadata),
     @(self.allowMultiple),
+    self.limit ?: [NSNull null],
   ];
 }
 @end
@@ -244,18 +249,20 @@
                   codec:FLTImagePickerApiGetCodec()];
     if (api) {
       NSCAssert([api respondsToSelector:@selector
-                     (pickMultiImageWithMaxSize:quality:fullMetadata:completion:)],
+                     (pickMultiImageWithMaxSize:quality:fullMetadata:limit:completion:)],
                 @"FLTImagePickerApi api (%@) doesn't respond to "
-                @"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)",
+                @"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:limit:completion:)",
                 api);
       [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
         NSArray *args = message;
         FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0);
         NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1);
         BOOL arg_requestFullMetadata = [GetNullableObjectAtIndex(args, 2) boolValue];
+        NSNumber *arg_limit = GetNullableObjectAtIndex(args, 3);
         [api pickMultiImageWithMaxSize:arg_maxSize
                                quality:arg_imageQuality
                           fullMetadata:arg_requestFullMetadata
+                                 limit:arg_limit
                             completion:^(NSArray<NSString *> *_Nullable output,
                                          FlutterError *_Nullable error) {
                               callback(wrapResult(output, error));
diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
index 4c302ca..9616613 100644
--- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
+++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
@@ -133,12 +133,19 @@
       throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
     }
 
+    final int? limit = options.limit;
+    if (limit != null && limit < 2) {
+      throw ArgumentError.value(limit, 'limit', 'cannot be lower than 2');
+    }
+
     // TODO(stuartmorgan): Remove the cast once Pigeon supports non-nullable
     //  generics, https://github.com/flutter/flutter/issues/97848
     return (await _hostApi.pickMultiImage(
-            MaxSize(width: maxWidth, height: maxHeight),
-            imageQuality,
-            options.imageOptions.requestFullMetadata))
+      MaxSize(width: maxWidth, height: maxHeight),
+      imageQuality,
+      options.imageOptions.requestFullMetadata,
+      limit,
+    ))
         .cast<String>();
   }
 
@@ -210,11 +217,28 @@
       MediaOptions mediaOptions) {
     final MaxSize maxSize =
         _imageOptionsToMaxSizeWithValidation(mediaOptions.imageOptions);
+
+    final bool allowMultiple = mediaOptions.allowMultiple;
+    final int? limit = mediaOptions.limit;
+
+    if (!allowMultiple && limit != null) {
+      throw ArgumentError.value(
+        allowMultiple,
+        'allowMultiple',
+        'cannot be false, when limit is not null',
+      );
+    }
+
+    if (limit != null && limit < 2) {
+      throw ArgumentError.value(limit, 'limit', 'cannot be lower than 2');
+    }
+
     return MediaSelectionOptions(
       maxSize: maxSize,
       imageQuality: mediaOptions.imageOptions.imageQuality,
       requestFullMetadata: mediaOptions.imageOptions.requestFullMetadata,
       allowMultiple: mediaOptions.allowMultiple,
+      limit: mediaOptions.limit,
     );
   }
 
diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
index 0ab7d1b..b3e785a 100644
--- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
+++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
@@ -1,9 +1,9 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v13.0.0), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
 
 import 'dart:async';
 import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
@@ -11,6 +11,13 @@
 import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
 import 'package:flutter/services.dart';
 
+PlatformException _createConnectionError(String channelName) {
+  return PlatformException(
+    code: 'channel-error',
+    message: 'Unable to establish connection on channel: "$channelName".',
+  );
+}
+
 List<Object?> wrapResponse(
     {Object? result, PlatformException? error, bool empty = false}) {
   if (empty) {
@@ -64,6 +71,7 @@
     this.imageQuality,
     required this.requestFullMetadata,
     required this.allowMultiple,
+    this.limit,
   });
 
   MaxSize maxSize;
@@ -74,12 +82,15 @@
 
   bool allowMultiple;
 
+  int? limit;
+
   Object encode() {
     return <Object?>[
       maxSize.encode(),
       imageQuality,
       requestFullMetadata,
       allowMultiple,
+      limit,
     ];
   }
 
@@ -90,6 +101,7 @@
       imageQuality: result[1] as int?,
       requestFullMetadata: result[2]! as bool,
       allowMultiple: result[3]! as bool,
+      limit: result[4] as int?,
     );
   }
 }
@@ -158,117 +170,122 @@
   /// available for dependency injection.  If it is left null, the default
   /// BinaryMessenger will be used which routes to the host platform.
   ImagePickerApi({BinaryMessenger? binaryMessenger})
-      : _binaryMessenger = binaryMessenger;
-  final BinaryMessenger? _binaryMessenger;
+      : __pigeon_binaryMessenger = binaryMessenger;
+  final BinaryMessenger? __pigeon_binaryMessenger;
 
-  static const MessageCodec<Object?> codec = _ImagePickerApiCodec();
+  static const MessageCodec<Object?> pigeonChannelCodec =
+      _ImagePickerApiCodec();
 
-  Future<String?> pickImage(SourceSpecification arg_source, MaxSize arg_maxSize,
-      int? arg_imageQuality, bool arg_requestFullMetadata) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel.send(<Object?>[
-      arg_source,
-      arg_maxSize,
-      arg_imageQuality,
-      arg_requestFullMetadata
-    ]) as List<Object?>?;
-    if (replyList == null) {
+  Future<String?> pickImage(SourceSpecification source, MaxSize maxSize,
+      int? imageQuality, bool requestFullMetadata) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+            .send(<Object?>[source, maxSize, imageQuality, requestFullMetadata])
+        as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
-      );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
     } else {
-      return (replyList[0] as String?);
+      return (__pigeon_replyList[0] as String?);
     }
   }
 
-  Future<List<String?>> pickMultiImage(MaxSize arg_maxSize,
-      int? arg_imageQuality, bool arg_requestFullMetadata) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage',
-        codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel.send(
-            <Object?>[arg_maxSize, arg_imageQuality, arg_requestFullMetadata])
+  Future<List<String?>> pickMultiImage(MaxSize maxSize, int? imageQuality,
+      bool requestFullMetadata, int? limit) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+            .send(<Object?>[maxSize, imageQuality, requestFullMetadata, limit])
         as List<Object?>?;
-    if (replyList == null) {
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
-      );
-    } else if (replyList[0] == null) {
+    } else if (__pigeon_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as List<Object?>?)!.cast<String?>();
+      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
     }
   }
 
   Future<String?> pickVideo(
-      SourceSpecification arg_source, int? arg_maxDurationSeconds) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel
-        .send(<Object?>[arg_source, arg_maxDurationSeconds]) as List<Object?>?;
-    if (replyList == null) {
+      SourceSpecification source, int? maxDurationSeconds) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+        .send(<Object?>[source, maxDurationSeconds]) as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
-      );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
     } else {
-      return (replyList[0] as String?);
+      return (__pigeon_replyList[0] as String?);
     }
   }
 
   /// Selects images and videos and returns their paths.
   Future<List<String?>> pickMedia(
-      MediaSelectionOptions arg_mediaSelectionOptions) async {
-    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia', codec,
-        binaryMessenger: _binaryMessenger);
-    final List<Object?>? replyList = await channel
-        .send(<Object?>[arg_mediaSelectionOptions]) as List<Object?>?;
-    if (replyList == null) {
+      MediaSelectionOptions mediaSelectionOptions) async {
+    const String __pigeon_channelName =
+        'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia';
+    final BasicMessageChannel<Object?> __pigeon_channel =
+        BasicMessageChannel<Object?>(
+      __pigeon_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: __pigeon_binaryMessenger,
+    );
+    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+        .send(<Object?>[mediaSelectionOptions]) as List<Object?>?;
+    if (__pigeon_replyList == null) {
+      throw _createConnectionError(__pigeon_channelName);
+    } else if (__pigeon_replyList.length > 1) {
       throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
+        code: __pigeon_replyList[0]! as String,
+        message: __pigeon_replyList[1] as String?,
+        details: __pigeon_replyList[2],
       );
-    } else if (replyList.length > 1) {
-      throw PlatformException(
-        code: replyList[0]! as String,
-        message: replyList[1] as String?,
-        details: replyList[2],
-      );
-    } else if (replyList[0] == null) {
+    } else if (__pigeon_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as List<Object?>?)!.cast<String?>();
+      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
     }
   }
 }
diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart
index 0e69598..d8ae895 100644
--- a/packages/image_picker/image_picker_ios/pigeons/messages.dart
+++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart
@@ -26,12 +26,14 @@
     this.imageQuality,
     required this.requestFullMetadata,
     required this.allowMultiple,
+    this.limit,
   });
 
   MaxSize maxSize;
   int? imageQuality;
   bool requestFullMetadata;
   bool allowMultiple;
+  int? limit;
 }
 
 // Corresponds to `CameraDevice` from the platform interface package.
@@ -53,9 +55,9 @@
   String? pickImage(SourceSpecification source, MaxSize maxSize,
       int? imageQuality, bool requestFullMetadata);
   @async
-  @ObjCSelector('pickMultiImageWithMaxSize:quality:fullMetadata:')
+  @ObjCSelector('pickMultiImageWithMaxSize:quality:fullMetadata:limit:')
   List<String?> pickMultiImage(
-      MaxSize maxSize, int? imageQuality, bool requestFullMetadata);
+      MaxSize maxSize, int? imageQuality, bool requestFullMetadata, int? limit);
   @async
   @ObjCSelector('pickVideoWithSource:maxDuration:')
   String? pickVideo(SourceSpecification source, int? maxDurationSeconds);
diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml
index c1b3c8d..70d578b 100755
--- a/packages/image_picker/image_picker_ios/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/pubspec.yaml
@@ -2,11 +2,11 @@
 description: iOS implementation of the image_picker plugin.
 repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.8.9+2
+version: 0.8.10
 
 environment:
-  sdk: ^3.2.3
-  flutter: ">=3.16.6"
+  sdk: ^3.3.0
+  flutter: ">=3.19.0"
 
 flutter:
   plugin:
@@ -19,16 +19,16 @@
 dependencies:
   flutter:
     sdk: flutter
-  image_picker_platform_interface: ^2.8.0
+  image_picker_platform_interface: ^2.10.0
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
   mockito: 5.4.4
-  pigeon: ^13.0.0
+  pigeon: ^17.0.0
 
 topics:
   - camera
   - image-picker
   - files
-  - file-selection
+  - file-selection
\ No newline at end of file
diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
index 77fa322..c2e5f74 100644
--- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
+++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
@@ -61,12 +61,14 @@
     MaxSize maxSize,
     int? imageQuality,
     bool requestFullMetadata,
+    int? limit,
   ) async {
     calls.add(_LoggedMethodCall('pickMultiImage', arguments: <String, dynamic>{
       'maxWidth': maxSize.width,
       'maxHeight': maxSize.height,
       'imageQuality': imageQuality,
       'requestFullMetadata': requestFullMetadata,
+      'limit': limit,
     }));
     return returnValue as List<String?>;
   }
@@ -80,6 +82,7 @@
       'imageQuality': mediaSelectionOptions.imageQuality,
       'requestFullMetadata': mediaSelectionOptions.requestFullMetadata,
       'allowMultiple': mediaSelectionOptions.allowMultiple,
+      'limit': mediaSelectionOptions.limit,
     }));
     return returnValue as List<String?>;
   }
@@ -329,6 +332,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -370,6 +374,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -377,6 +382,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -384,6 +390,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -391,6 +398,7 @@
                 'maxHeight': 20.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -398,6 +406,7 @@
                 'maxHeight': null,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -405,6 +414,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -412,6 +422,7 @@
                 'maxHeight': 20.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -769,6 +780,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -810,6 +822,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -817,6 +830,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -824,6 +838,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -831,6 +846,7 @@
                 'maxHeight': 20.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -838,6 +854,7 @@
                 'maxHeight': null,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -845,6 +862,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -852,6 +870,7 @@
                 'maxHeight': 20.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -903,7 +922,8 @@
             'maxHeight': null,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
         ],
       );
@@ -959,6 +979,16 @@
           imageQuality: 70,
         ),
       ));
+      await picker.getMedia(
+          options: MediaOptions(
+        allowMultiple: true,
+        imageOptions: ImageOptions.createAndValidate(
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+          imageQuality: 70,
+        ),
+        limit: 5,
+      ));
 
       expect(
         log.calls,
@@ -968,49 +998,64 @@
             'maxHeight': null,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': 10.0,
             'maxHeight': null,
             'imageQuality': 70,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': null,
             'maxHeight': 10.0,
             'imageQuality': 70,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
           const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
             'maxWidth': 10.0,
             'maxHeight': 20.0,
             'imageQuality': 70,
             'requestFullMetadata': true,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
+          }),
+          const _LoggedMethodCall('pickMedia', arguments: <String, dynamic>{
+            'maxWidth': 10.0,
+            'maxHeight': 20.0,
+            'imageQuality': 70,
+            'requestFullMetadata': true,
+            'allowMultiple': true,
+            'limit': 5,
           }),
         ],
       );
@@ -1032,7 +1077,8 @@
             'maxHeight': null,
             'imageQuality': null,
             'requestFullMetadata': false,
-            'allowMultiple': true
+            'allowMultiple': true,
+            'limit': null,
           }),
         ],
       );
@@ -1053,7 +1099,8 @@
             'maxHeight': null,
             'imageQuality': null,
             'requestFullMetadata': true,
-            'allowMultiple': false
+            'allowMultiple': false,
+            'limit': null,
           }),
         ],
       );
@@ -1101,6 +1148,36 @@
       );
     });
 
+    test('does not accept a invalid limit argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMedia(
+            options: const MediaOptions(
+          allowMultiple: true,
+          limit: -1,
+        )),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMedia(
+            options: const MediaOptions(
+          allowMultiple: true,
+          limit: 0,
+        )),
+        throwsArgumentError,
+      );
+    });
+
+    test('does not accept a not null limit when allowMultiple is false', () {
+      expect(
+        () => picker.getMedia(
+          options: const MediaOptions(allowMultiple: false, limit: 5),
+        ),
+        throwsArgumentError,
+      );
+    });
+
     test('handles a empty path response gracefully', () async {
       log.returnValue = <String>[];
 
@@ -1501,6 +1578,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -1543,6 +1621,16 @@
           ),
         ),
       );
+      await picker.getMultiImageWithOptions(
+        options: const MultiImagePickerOptions(
+          imageOptions: ImageOptions(
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70,
+          ),
+          limit: 5,
+        ),
+      );
 
       expect(
         log.calls,
@@ -1553,6 +1641,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1560,6 +1649,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1567,6 +1657,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1574,6 +1665,7 @@
                 'maxHeight': 20.0,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1581,6 +1673,7 @@
                 'maxHeight': null,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1588,6 +1681,7 @@
                 'maxHeight': 10.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
           const _LoggedMethodCall('pickMultiImage',
               arguments: <String, dynamic>{
@@ -1595,6 +1689,15 @@
                 'maxHeight': 20.0,
                 'imageQuality': 70,
                 'requestFullMetadata': true,
+                'limit': null,
+              }),
+          const _LoggedMethodCall('pickMultiImage',
+              arguments: <String, dynamic>{
+                'maxWidth': 10.0,
+                'maxHeight': 20.0,
+                'imageQuality': 70,
+                'requestFullMetadata': true,
+                'limit': 5,
               }),
         ],
       );
@@ -1642,6 +1745,27 @@
       );
     });
 
+    test('does not accept a invalid limit argument', () {
+      log.returnValue = <String>['0', '1'];
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            limit: -1,
+          ),
+        ),
+        throwsArgumentError,
+      );
+
+      expect(
+        () => picker.getMultiImageWithOptions(
+          options: const MultiImagePickerOptions(
+            limit: 0,
+          ),
+        ),
+        throwsArgumentError,
+      );
+    });
+
     test('handles an empty response', () async {
       log.returnValue = <String>[];
 
@@ -1661,6 +1785,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': true,
+                'limit': null,
               }),
         ],
       );
@@ -1683,6 +1808,7 @@
                 'maxHeight': null,
                 'imageQuality': null,
                 'requestFullMetadata': false,
+                'limit': null,
               }),
         ],
       );
diff --git a/packages/image_picker/image_picker_ios/test/test_api.g.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart
index a2d0266..4208c68 100644
--- a/packages/image_picker/image_picker_ios/test/test_api.g.dart
+++ b/packages/image_picker/image_picker_ios/test/test_api.g.dart
@@ -1,9 +1,9 @@
 // Copyright 2013 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-// Autogenerated from Pigeon (v13.0.0), do not edit directly.
+// Autogenerated from Pigeon (v17.0.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers
 // ignore_for_file: avoid_relative_lib_imports
 import 'dart:async';
 import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
@@ -49,13 +49,14 @@
 abstract class TestHostImagePickerApi {
   static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
       TestDefaultBinaryMessengerBinding.instance;
-  static const MessageCodec<Object?> codec = _TestHostImagePickerApiCodec();
+  static const MessageCodec<Object?> pigeonChannelCodec =
+      _TestHostImagePickerApiCodec();
 
   Future<String?> pickImage(SourceSpecification source, MaxSize maxSize,
       int? imageQuality, bool requestFullMetadata);
 
   Future<List<String?>> pickMultiImage(
-      MaxSize maxSize, int? imageQuality, bool requestFullMetadata);
+      MaxSize maxSize, int? imageQuality, bool requestFullMetadata, int? limit);
 
   Future<String?> pickVideo(
       SourceSpecification source, int? maxDurationSeconds);
@@ -66,15 +67,17 @@
   static void setup(TestHostImagePickerApi? api,
       {BinaryMessenger? binaryMessenger}) {
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage', codec,
-          binaryMessenger: binaryMessenger);
+      final BasicMessageChannel<Object?> __pigeon_channel =
+          BasicMessageChannel<Object?>(
+              'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage',
+              pigeonChannelCodec,
+              binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
               'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickImage was null.');
@@ -104,16 +107,17 @@
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+      final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
+              Object?>(
           'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage',
-          codec,
+          pigeonChannelCodec,
           binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
               'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage was null.');
@@ -125,9 +129,10 @@
           final bool? arg_requestFullMetadata = (args[2] as bool?);
           assert(arg_requestFullMetadata != null,
               'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMultiImage was null, expected non-null bool.');
+          final int? arg_limit = (args[3] as int?);
           try {
-            final List<String?> output = await api.pickMultiImage(
-                arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!);
+            final List<String?> output = await api.pickMultiImage(arg_maxSize!,
+                arg_imageQuality, arg_requestFullMetadata!, arg_limit);
             return <Object?>[output];
           } on PlatformException catch (e) {
             return wrapResponse(error: e);
@@ -139,15 +144,17 @@
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo', codec,
-          binaryMessenger: binaryMessenger);
+      final BasicMessageChannel<Object?> __pigeon_channel =
+          BasicMessageChannel<Object?>(
+              'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo',
+              pigeonChannelCodec,
+              binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
               'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickVideo was null.');
@@ -171,15 +178,17 @@
       }
     }
     {
-      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia', codec,
-          binaryMessenger: binaryMessenger);
+      final BasicMessageChannel<Object?> __pigeon_channel =
+          BasicMessageChannel<Object?>(
+              'dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia',
+              pigeonChannelCodec,
+              binaryMessenger: binaryMessenger);
       if (api == null) {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel, null);
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel, null);
       } else {
         _testBinaryMessengerBinding!.defaultBinaryMessenger
-            .setMockDecodedMessageHandler<Object?>(channel,
+            .setMockDecodedMessageHandler<Object?>(__pigeon_channel,
                 (Object? message) async {
           assert(message != null,
               'Argument for dev.flutter.pigeon.image_picker_ios.ImagePickerApi.pickMedia was null.');