Image Picker Kit

A complete, all-in-one Flutter plugin for a native image workflow: pick, crop, and compress.

Image Picker Kit Demo

Description

A complete, all-in-one Flutter plugin for image workflow: pick, crop, and compress. This package provides a native implementation (MethodChannel) for selecting and compressing, while integrating the image_cropper library. Native Compression (Pick-only) utilises the native compressors Bitmap.compress for Android and UIImage.jpegData for iOS. While cropping, it utilises image_cropper's built-in compression parameters.


Installation

  1. Add the package to your pubspec.yaml:

    dependencies:
      image_picker_kit: ^1.0.0 # Replace with the latest version
    
  2. Run flutter pub get.


Platform Setup

This plugin requires native setup on both iOS and Android.

iOS

Add the NSPhotoLibraryUsageDescription key to your ios/Runner/Info.plist file. This is required for any app that accesses the photo library.

<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to the photo library to select and crop images.</string>

Android

You must complete all three steps for the plugin to work correctly on Android.

  1. Add UCropActivity to AndroidManifest.xml

The integrated image_cropper plugin requires the UCropActivity. Add it inside the

<manifest ...>
    <application ...>
        <activity android:name=".MainActivity" ...>
          </activity>

        <activity
            android:name="com.yalantis.ucrop.UCropActivity"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
        
        <meta-data ... />
    </application>
</manifest>
  1. Enable MultiDex

Adding native plugins can cause your app to exceed Android's 64K method limit. Enable multiDex in your android/app/build.gradle.kts (or .gradle) file. This allows your app to be split into multiple .dex files, preventing crashes on older Android versions.

android {
    defaultConfig {
        // ...
        multiDexEnabled = true
    }
}
  1. Add ExifInterface Dependency

To prevent a NoClassDefFoundError when the cropper tries to read image rotation and metadata, add the exifinterface dependency to your android/app/build.gradle.kts. This manually includes the ExifInterface class, which uCrop needs but is sometimes removed by build tools.

dependencies {
    implementation("androidx.exifinterface:exifinterface:1.3.7")
    // ... other dependencies
}

Usage

Import the package and create an instance of MediaPickerKit.

import 'package.image_picker_kit/image_picker_kit.dart';

// Create an instance of the kit
final MediaPickerKit _pickerKit = MediaPickerKit();
  1. Pick Image This method picks an image and uses the native compressor.
Future<void> selectImage() async {
  final File? resultFile = await _pickerKit.pickImageFromGallery(
    compressQuality: 80, // 1-100
  );

  if (resultFile != null) {
    // Use the compressed file
  } else {
    // User cancelled
  }
}
  1. Pick and Crop Image This method picks an image, opens the cropper UI, and uses the image_cropper's built-in compression.
Future<void> selectAndCropImage() async {
  final File? resultFile = await _pickerKit.pickAndCropImageFromGallery(
    // These settings are passed to image_cropper
    compressQuality: 80, // 1-100
    compressFormat: ImageCompressFormat.jpg,
    aspectRatio: const CropAspectRatio(ratioX: 16, ratioY: 9),
    androidUiSettings: const AndroidUiSettings(
      toolbarTitle: 'Crop Image',
      toolbarColor: Colors.blue,
      toolbarWidgetColor: Colors.white,
    ),
    iOSUiSettings: const IOSUiSettings(
      title: 'Crop Image',
    ),
  );

  if (resultFile != null) {
    // Use the cropped and compressed file
  } else {
    // User cancelled
  }
}

Full Example

Here is a simple example of how to use both methods.

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

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final MediaPickerKit _pickerKit = MediaPickerKit();
  String _resultMessage = 'No image chosen yet';
  File? _imageFile;

  Future<void> _handlePickingLogic(Future<File?> Function() pickingFuture) async {
    setState(() {
      _resultMessage = 'Loading...';
      _imageFile = null;
    });

    final File? resultFile = await pickingFuture();

    setState(() {
      if (resultFile != null) {
        _resultMessage = 'SUCCESS';
        _imageFile = resultFile;
      } else {
        _resultMessage = 'CANCELLED';
        _imageFile = null;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Image Picker Kit Example')),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => _handlePickingLogic(
                    () => _pickerKit.pickImageFromGallery(compressQuality: 10),
                  ),
                  child: const Text('Pick (Only) & Compress'),
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () => _handlePickingLogic(
                    () => _pickerKit.pickAndCropImageFromGallery(compressQuality: 10),
                  ),
                  child: const Text('Pick, Crop & Compress'),
                ),
                const SizedBox(height: 20),
                Text(_resultMessage),
                const SizedBox(height: 20),
                if (_imageFile != null)
                  Image.file(
                    _imageFile!,
                    height: 300,
                    width: 300,
                    fit: BoxFit.cover,
                  )
                else
                  Container(
                    height: 300,
                    width: 300,
                    color: Colors.grey[300],
                    alignment: Alignment.center,
                    child: const Text('Image Preview'),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Credits

This package provides a unified API and custom native implementations for picking/compressing, but the powerful cropping UI is provided entirely by the image_cropper package.