Creating an Image Picker Widget with Cropping in Flutter
Modern mobile applications often require users to select and customize images, whether it's for a profile picture, a product listing, or a document upload. Flutter provides robust packages to handle image picking and advanced image manipulation like cropping. This article will guide you through creating a reusable image picker widget in Flutter that includes a cropping feature, ensuring a professional and user-friendly experience.
Why Image Picking with Cropping?
While picking an image from the gallery or camera is straightforward, users frequently need to adjust the image to fit specific aspect ratios or to focus on a particular area. Implementing cropping functionality directly within your app enhances user control and reduces the need for external photo editing, leading to a smoother user journey.
Prerequisites and Dependencies
To implement an image picker with cropping, we'll leverage two powerful Flutter packages:
image_picker: Provides a platform-agnostic way to pick images from the device's image library or camera.image_cropper: Offers a customizable UI for cropping images.
Add these dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
image_picker: ^1.1.2 # Use the latest version
image_cropper: ^5.0.1 # Use the latest version
After adding the dependencies, run flutter pub get.
Platform-Specific Setup
Both packages require minimal platform-specific configuration:
Android
No extra permissions are usually required in AndroidManifest.xml for image_picker to access the gallery. If you plan to use the camera, ensure you have:
<uses-permission android:name="android.permission.CAMERA" />
For image_cropper, ensure your android/app/build.gradle file's minSdkVersion is 21 or higher:
android {
defaultConfig {
minSdkVersion 21
}
}
iOS
Add the following keys to your ios/Runner/Info.plist file:
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library to pick images.</string>
<key>NSCameraUsageDescription</key>
<string>This app needs access to your camera to take photos.</string>
Core Logic: Picking and Cropping an Image
Let's first understand how to pick and then crop an image before encapsulating it into a widget.
1. Picking an Image
Using ImagePicker, you can pick an image from the gallery or camera:
import 'package:image_picker/image_picker.dart';
Future<XFile?> pickImageFromGallery() async {
final ImagePicker picker = ImagePicker();
final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
return pickedFile;
}
2. Cropping the Image
Once you have an XFile (which contains the path to the selected image), you can pass its path to ImageCropper:
import 'package:image_cropper/image_cropper.dart';
import 'package:flutter/material.dart'; // For Colors
Future<CroppedFile?> cropImage(String imagePath) async {
final croppedFile = await ImageCropper().cropImage(
sourcePath: imagePath,
aspectRatioPresets: [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Crop Image',
toolbarColor: Colors.deepPurple,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Crop Image',
),
],
);
return croppedFile;
}
The aspectRatioPresets define common aspect ratios available to the user. uiSettings allow you to customize the look and feel of the cropper UI for both Android and iOS platforms, ensuring it matches your app's theme.
Creating the Reusable Image Picker Widget
Now, let's combine these functionalities into a single, reusable StatefulWidget.
This widget will:
- Provide a button to trigger the image picking and cropping process.
- Display the selected and cropped image.
- Offer a callback to return the final
Fileobject of the cropped image to its parent widget.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
class ImageCropperPicker extends StatefulWidget {
/// Callback function that returns the cropped image File.
final Function(File)? onImagePicked;
/// Text to display on the pick image button.
final String buttonText;
/// Initial image to display if any.
final File? initialImage;
const ImageCropperPicker({
Key? key,
this.onImagePicked,
this.buttonText = "Pick & Crop Image",
this.initialImage,
}) : super(key: key);
@override
State<ImageCropperPicker> createState() => _ImageCropperPickerState();
}
class _ImageCropperPickerState extends State<ImageCropperPicker> {
File? _selectedImage;
@override
void initState() {
super.initState();
_selectedImage = widget.initialImage;
}
Future<void> _pickAndCropImage() async {
final ImagePicker picker = ImagePicker();
// You can also add ImageSource.camera for taking new photos
final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
final croppedFile = await ImageCropper().cropImage(
sourcePath: pickedFile.path,
aspectRatioPresets: [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Crop Image',
toolbarColor: Colors.deepPurple,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Crop Image',
),
],
);
if (croppedFile != null) {
setState(() {
_selectedImage = File(croppedFile.path);
});
widget.onImagePicked?.call(_selectedImage!);
}
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: _pickAndCropImage,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.grey[200],
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8.0),
),
child: _selectedImage != null
? ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.file(
_selectedImage!,
width: 150,
height: 150,
fit: BoxFit.cover,
),
)
: const Center(
child: Icon(
Icons.add_a_photo,
color: Colors.grey,
size: 50,
),
),
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _pickAndCropImage,
child: Text(widget.buttonText),
),
if (_selectedImage != null) ...[
const SizedBox(height: 10),
Text(
'Image selected!',
style: TextStyle(color: Colors.green[700]),
),
]
],
);
}
}
Using the Widget
Integrating the ImageCropperPicker into your application is straightforward:
import 'dart:io';
import 'package:flutter/material.dart';
// Assuming ImageCropperPicker is in 'image_cropper_picker.dart'
import 'package:your_app_name/image_cropper_picker.dart'; // Adjust path accordingly
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
File? _userProfileImage;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profile Picture Setup'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Upload your profile picture:',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 30),
ImageCropperPicker(
buttonText: 'Choose Profile Image',
initialImage: _userProfileImage, // Pass initial image if available
onImagePicked: (File image) {
setState(() {
_userProfileImage = image;
});
// Here you can upload the 'image' file to a server,
// save its path, or display it elsewhere in your app.
print('Picked and cropped image path: ${image.path}');
},
),
const SizedBox(height: 30),
if (_userProfileImage != null)
CircleAvatar(
radius: 60,
backgroundImage: FileImage(_userProfileImage!),
)
else
const CircleAvatar(
radius: 60,
child: Icon(Icons.person, size: 60),
),
],
),
),
);
}
}
// Example main function to run the app
void main() {
runApp(MaterialApp(
home: HomePage(),
));
}
Conclusion
By integrating image_picker and image_cropper, you can create a highly functional and aesthetically pleasing image selection widget for your Flutter applications. This reusable component not only streamlines your development process but also provides a superior user experience by allowing users to precisely crop their images directly within the app. Remember to always consider platform-specific configurations and user permissions for a smooth deployment.