image

13 Jan 2026

9K

35K

Creating a Multi-Select Image Picker Widget in Flutter

Image picking is a fundamental feature in many mobile applications, from social media platforms to e-commerce apps. While a single image picker is often sufficient, there are numerous scenarios where users need to select multiple images simultaneously – think about uploading a gallery, attaching multiple documents, or sharing a collection of photos. This article will guide you through creating a robust multi-select image picker widget in Flutter, leveraging the popular image_picker package.

Introduction to Multi-Select Image Picker

A multi-select image picker allows users to choose several images from their device's gallery or camera roll in a single interaction. Once selected, these images are typically displayed within the app, giving users a visual confirmation and often the ability to reorder or remove them before final submission. Our widget will focus on the core functionality: picking multiple images and displaying them in an interactive grid.

Prerequisites

Before we dive into the code, ensure you have a Flutter development environment set up. We'll be using the image_picker package. Add it to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  image_picker: ^1.1.2 # Use the latest stable version

After adding the dependency, run flutter pub get.

Platform-Specific Configurations

The image_picker package requires specific permissions for Android and iOS. Always refer to the official package documentation for the most up-to-date configurations.

Android

No extra configuration required if targeting Android 10 (API level 29) or higher, as the package uses platform intents that handle permissions automatically. For older Android versions, you might need to add permissions to AndroidManifest.xml.

iOS

Add the following keys to your Info.plist file (located at ios/Runner/Info.plist):


<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library to pick images.</string>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for video recording.</string>

Core Logic: Picking and Displaying Images

The core of our widget involves two main parts:

  1. Picking Images: Using ImagePicker().pickMultiImage() to open the gallery and allow multiple selections.
  2. Displaying Images: Using a GridView.builder to show the selected images, each with an option to remove it.

Step-by-Step Implementation

1. Create a Stateful Widget

We'll need a Stateful Widget to manage the list of selected images.


import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

class MultiImagePickerWidget extends StatefulWidget {
  final ValueChanged<List<File>> onImagesSelected;

  const MultiImagePickerWidget({
    Key? key,
    required this.onImagesSelected,
  }) : super(key: key);

  @override
  _MultiImagePickerWidgetState createState() => _MultiImagePickerWidgetState();
}

class _MultiImagePickerWidgetState extends State<MultiImagePickerWidget> {
  List<XFile> _selectedXFiles = [];
  final ImagePicker _picker = ImagePicker();

  // ... rest of the state class
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Image picker button
        // Grid view for selected images
      ],
    );
  }
}

2. Implement the Image Picking Logic

Create a method _pickImages() that uses _picker.pickMultiImage(). This method returns a List<XFile>, which are references to the selected image files.


  Future<void> _pickImages() async {
    final List<XFile>? images = await _picker.pickMultiImage();

    if (images != null && images.isNotEmpty) {
      setState(() {
        _selectedXFiles.addAll(images);
      });
      // Convert XFile to File for external use if needed
      widget.onImagesSelected(_selectedXFiles.map((xFile) => File(xFile.path)).toList());
    }
  }

3. Display Selected Images in a Grid

Use a GridView.builder to efficiently display a potentially large number of images. Each image will be shown using Image.file() and an `IconButton` to allow removal.


  // ... inside _MultiImagePickerWidgetState build method ...
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton.icon(
          onPressed: _pickImages,
          icon: const Icon(Icons.add_photo_alternate),
          label: const Text('Add Images'),
        ),
        const SizedBox(height: 10),
        Expanded(
          child: _selectedXFiles.isEmpty
              ? const Center(
                  child: Text('No images selected.'),
                )
              : GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 3,
                    crossAxisSpacing: 8.0,
                    mainAxisSpacing: 8.0,
                  ),
                  itemCount: _selectedXFiles.length,
                  itemBuilder: (BuildContext context, int index) {
                    final XFile imageXFile = _selectedXFiles[index];
                    return Stack(
                      children: [
                        Positioned.fill(
                          child: Image.file(
                            File(imageXFile.path),
                            fit: BoxFit.cover,
                          ),
                        ),
                        Positioned(
                          top: 0,
                          right: 0,
                          child: GestureDetector(
                            onTap: () => _removeImage(imageXFile),
                            child: Container(
                              padding: const EdgeInsets.all(2),
                              decoration: BoxDecoration(
                                color: Colors.black54,
                                borderRadius: BorderRadius.circular(10),
                              ),
                              child: const Icon(
                                Icons.close,
                                color: Colors.white,
                                size: 18,
                              ),
                            ),
                          ),
                        ),
                      ],
                    );
                  },
                ),
        ),
      ],
    );
  }

4. Implement Image Removal Logic

Add a method _removeImage() to remove an image from the _selectedXFiles list.


  void _removeImage(XFile imageToRemove) {
    setState(() {
      _selectedXFiles.remove(imageToRemove);
    });
    // Update parent widget's image list
    widget.onImagesSelected(_selectedXFiles.map((xFile) => File(xFile.path)).toList());
  }

Full Widget Code

Here is the complete code for the MultiImagePickerWidget:


import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

class MultiImagePickerWidget extends StatefulWidget {
  final ValueChanged<List<File>> onImagesSelected;

  const MultiImagePickerWidget({
    Key? key,
    required this.onImagesSelected,
  }) : super(key: key);

  @override
  _MultiImagePickerWidgetState createState() => _MultiImagePickerWidgetState();
}

class _MultiImagePickerWidgetState extends State<MultiImagePickerWidget> {
  List<XFile> _selectedXFiles = [];
  final ImagePicker _picker = ImagePicker();

  Future<void> _pickImages() async {
    final List<XFile>? images = await _picker.pickMultiImage();

    if (images != null && images.isNotEmpty) {
      setState(() {
        _selectedXFiles.addAll(images);
      });
      // Convert XFile to File for external use if needed
      widget.onImagesSelected(_selectedXFiles.map((xFile) => File(xFile.path)).toList());
    }
  }

  void _removeImage(XFile imageToRemove) {
    setState(() {
      _selectedXFiles.remove(imageToRemove);
    });
    // Update parent widget's image list
    widget.onImagesSelected(_selectedXFiles.map((xFile) => File(xFile.path)).toList());
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton.icon(
          onPressed: _pickImages,
          icon: const Icon(Icons.add_photo_alternate),
          label: const Text('Add Images'),
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
            textStyle: const TextStyle(fontSize: 16),
          ),
        ),
        const SizedBox(height: 20),
        Expanded(
          child: _selectedXFiles.isEmpty
              ? const Center(
                  child: Text(
                    'No images selected.\nTap "Add Images" to pick some.',
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 16, color: Colors.grey),
                  ),
                )
              : GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 3,
                    crossAxisSpacing: 8.0,
                    mainAxisSpacing: 8.0,
                  ),
                  itemCount: _selectedXFiles.length,
                  itemBuilder: (BuildContext context, int index) {
                    final XFile imageXFile = _selectedXFiles[index];
                    return Card(
                      clipBehavior: Clip.antiAlias,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8.0),
                      ),
                      elevation: 2,
                      child: Stack(
                        children: [
                          Positioned.fill(
                            child: Image.file(
                              File(imageXFile.path),
                              fit: BoxFit.cover,
                              errorBuilder: (context, error, stackTrace) => const Center(
                                child: Icon(Icons.broken_image, color: Colors.grey),
                              ),
                            ),
                          ),
                          Positioned(
                            top: 4,
                            right: 4,
                            child: GestureDetector(
                              onTap: () => _removeImage(imageXFile),
                              child: Container(
                                padding: const EdgeInsets.all(3),
                                decoration: BoxDecoration(
                                  color: Colors.redAccent.withOpacity(0.8),
                                  shape: BoxShape.circle,
                                ),
                                child: const Icon(
                                  Icons.close,
                                  color: Colors.white,
                                  size: 16,
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }
}

How to Use the Widget

You can integrate this widget into any part of your Flutter application. Here's an example of how you might use it in your main.dart file:


import 'dart:io';
import 'package:flutter/material.dart';
import 'multi_image_picker_widget.dart'; // Assuming your widget is in this file

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multi-Select Image Picker Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<File> _finalSelectedImages = [];

  void _handleImagesSelected(List<File> images) {
    setState(() {
      _finalSelectedImages = images;
    });
    // You can now use _finalSelectedImages for upload or further processing
    print('Total images selected: ${_finalSelectedImages.length}');
    for (var image in _finalSelectedImages) {
      print('Image path: ${image.path}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Multi-Select Image Picker'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Expanded(
              child: MultiImagePickerWidget(
                onImagesSelected: _handleImagesSelected,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _finalSelectedImages.isEmpty
                  ? null
                  : () {
                      // Perform an action with the _finalSelectedImages
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: Text('Processing ${_finalSelectedImages.length} images!'),
                        ),
                      );
                      // e.g., upload images to a server
                    },
              child: Text(
                  'Submit Images (${_finalSelectedImages.length})'),
              style: ElevatedButton.styleFrom(
                minimumSize: const Size.fromHeight(50), // Make button full width
              ),
            ),
            const SizedBox(height: 20),
          ],
        ),
      ),
    );
  }
}

Customization and Enhancements

This widget provides a solid foundation. Here are some ideas for further customization and enhancements:

  • Max Image Limit: Add an optional parameter to limit the number of images a user can select.
  • Image Reordering: Implement drag-and-drop functionality to allow users to reorder selected images using ReorderableGridView (from a package) or a custom ReorderableList.
  • Loading Indicators: Show a loading spinner while images are being picked or processed.
  • Error Handling: Implement more robust error handling for image picking failures.
  • Image Compression: Before using File(imageXFile.path), you might want to compress large images to save bandwidth or storage using packages like flutter_image_compress.
  • Camera Support: While pickMultiImage only accesses the gallery, you could add an option to open the camera first for a single shot, then allow gallery selection.

Conclusion

Creating a multi-select image picker in Flutter is straightforward with the image_picker package. This article demonstrated how to build a functional and user-friendly widget that allows users to pick multiple images, display them in a grid, and remove them as needed. With the provided codebase, you have a strong starting point to integrate this common feature into your Flutter applications and enhance it further to meet specific project requirements.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is