Flutter - Build an Image Compressor App
Last Updated :
04 May, 2025
Many applications accept images of small size, to reduce image size we use different online applications to compress images while maintaining their quality. In this article, we will create an image compressor app in Flutter that compresses images with the help of available libraries in Flutter.
Step-by-Step Implementation
Step 1: Create a new Flutter Application
Create a new Flutter application using the command Prompt. To create a new app, write the following command and run it.
flutter create app_name
To know more about it refer this article: Creating a Simple Application in Flutter
Step 2: Adding the Dependency
To add the dependency to the pubspec.yaml file, add image_picker, flutter_image_compress, and path_provider as dependencies in the dependencies part of the pubspec.yaml file, as shown below:
Dart
dependencies:
flutter:
sdk: flutter
image_picker: ^1.1.2
flutter_image_compress: ^2.3.0
path_provider: ^2.1.5
Now run the below command in the terminal.
flutter pub get
Or
Run the below command in the terminal.
flutter pub add flutter_image_compress image_picker path_provider
Step 3 : Import dependencies
To use libraries, import all of them in the main.dart file,
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
Step 4 : Working With main.dart
Add the boilerplate code below in main.dart to create a basic structure with an AppBar and body using a Scaffold.
Dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ImageCompressorApp(),
debugShowCheckedModeBanner: false,
);
}
}
// This is the main widget of the app.
class ImageCompressorApp extends StatefulWidget {
@override
_ImageCompressorAppState createState() => _ImageCompressorAppState();
}
// This is the state class for the main widget.
class _ImageCompressorAppState extends State<ImageCompressorApp> {
// This function builds the user interface.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GFG Image Compression'),
// Set app bar color.
backgroundColor: Colors.green,
// Set text color
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
// Code Here
),
);
}
}
Step 5 : Initialize variables
Intialize required variables.
Dart
// Stores the original image.
File? _selectedImage;
// Stores the compressed image.
File? _compressedImage;
// Checks if compression is in progress.
bool _isCompressing = false;
// Stores error messages.
String? _error;
Step 6 : Create methods
Create methods to select an image from the gallery and compress it using the image_picker and flutter_image_compress packages.
Dart
// Function to pick an image from the gallery.
Future<void> pickImage() async {
try {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
// Store the selected image.
_selectedImage = File(pickedFile.path);
// Reset previous compressed image.
_compressedImage = null;
// Clear any previous error.
_error = null;
// Show that compression is starting.
_isCompressing = true;
});
// Compress the selected image.
await compressImage(_selectedImage!);
}
} catch (e) {
setState(() {
// Store error message.
_error = 'Error picking image: $e';
// Stop compressing state.
_isCompressing = false;
});
}
}
// Function to compress the selected image.
Future<void> compressImage(File image) async {
try {
// Get a temporary directory to store the compressed image.
final tempDir = await getTemporaryDirectory();
final targetPath = '${tempDir.path}/compressed_image.jpg';
// Compress the image and store it in the new location.
var result = await FlutterImageCompress.compressAndGetFile(
image.absolute.path,
targetPath,
// Set image quality (lower value = more compression).
quality: 85,
);
if (result != null) {
final compressedFile = File(result.path);
if (await compressedFile.exists()) {
final originalSize = await image.length();
final compressedSize = await compressedFile.length();
setState(() {
// Save the compressed image.
_compressedImage = compressedFile;
// Compression is done.
_isCompressing = false;
// Clear any error messages.
_error = null;
});
// Print the sizes of original and compressed images.
print('Original size: ${(originalSize / 1024).toStringAsFixed(2)} KB');
print('Compressed size: ${(compressedSize / 1024).toStringAsFixed(2)} KB');
} else {
throw Exception('Compressed file not found at path: $targetPath');
}
} else {
throw Exception('Compression returned null path');
}
} catch (e) {
setState(() {
// Store error message.
_error = 'Error compressing image: $e';
// Stop compressing state.
_isCompressing = false;
});
print('Error compressing image: $e');
}
}
Step 7 : Develop UI
- ElevatedButton : Used to select images from the gallery.
Dart
// Button to pick and compress an image.
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white
),
onPressed: _isCompressing
? null
: pickImage, // Disable when compressing.
child: Text(_isCompressing
? 'Compressing...' // Show progress message.
: 'Pick and Compress Image'),
),
- Error Container : Display the error wrapped in a container for the user.
Dart
if (_error != null) ...[
// Show error message if any.
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(8),
// Background color for error message.
color: Colors.red.shade100,
child: Text(
_error!,
// Red text color.
style: const TextStyle(color: Colors.red),
),
),
],
- Original & Compressed Image : Display both the original and compressed image files using the Image.file() widget.
Dart
if (_selectedImage != null) ...[
// Show original image.
const SizedBox(height: 16),
const Text(
'Original Image',
style:TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold
),
),
const SizedBox(height: 8),
Image.file(_selectedImage!),
],
if (_compressedImage != null) ...[
// Show compressed image.
const SizedBox(height: 16),
const Text(
'Compressed Image',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold
),
),
const SizedBox(height: 8),
Image.file(_compressedImage!),
]
- Show values : Display the original image size, compressed image size, and the percentage reduction using the code below.
Dart
// Show original and compressed image sizes.
FutureBuilder<List<int>>(
future: Future.wait([
_selectedImage!.length(),
_compressedImage!.length(),
]),
builder: (context, snapshot) {
if (snapshot.hasData) {
final originalSize = snapshot.data![0] / 1024;
final compressedSize = snapshot.data![1] / 1024;
final savings =((originalSize - compressedSize) / originalSize * 100);
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Original: ${originalSize.toStringAsFixed(2)} KB\n'
'Compressed: ${compressedSize.toStringAsFixed(2)} KB\n'
'Reduced by: ${savings.toStringAsFixed(1)}%',
style: const TextStyle(fontSize: 14),
),
);
}
// Return empty widget if no data.
return const SizedBox();
},
),
Complete Source Code
main.dart:
Dart
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ImageCompressorApp(),
debugShowCheckedModeBanner: false,
);
}
}
// This is the main widget of the app.
class ImageCompressorApp extends StatefulWidget {
@override
_ImageCompressorAppState createState() => _ImageCompressorAppState();
}
// This is the state class for the main widget.
class _ImageCompressorAppState extends State<ImageCompressorApp> {
// Stores the original image.
File? _selectedImage;
// Stores the compressed image.
File? _compressedImage;
// Checks if compression is in progress.
bool _isCompressing = false;
// Stores error messages.
String? _error;
// Function to pick an image from the gallery.
Future<void> pickImage() async {
try {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
// Store the selected image.
_selectedImage = File(pickedFile.path);
// Reset previous compressed image.
_compressedImage = null;
// Clear any previous error.
_error = null;
// Show that compression is starting.
_isCompressing = true;
});
// Compress the selected image.
await compressImage(_selectedImage!);
}
} catch (e) {
setState(() {
// Store error message.
_error = 'Error picking image: $e';
// Stop compressing state.
_isCompressing = false;
});
}
}
// Function to compress the selected image.
Future<void> compressImage(File image) async {
try {
// Get a temporary directory to store the compressed image.
final tempDir = await getTemporaryDirectory();
final targetPath = '${tempDir.path}/compressed_image.jpg';
// Compress the image and store it in the new location.
var result = await FlutterImageCompress.compressAndGetFile(
image.absolute.path,
targetPath,
// Set image quality (lower value = more compression).
quality: 85,
);
if (result != null) {
final compressedFile = File(result.path);
if (await compressedFile.exists()) {
final originalSize = await image.length();
final compressedSize = await compressedFile.length();
setState(() {
// Save the compressed image.
_compressedImage = compressedFile;
// Compression is done.
_isCompressing = false;
// Clear any error messages.
_error = null;
});
// Print the sizes of original and compressed images.
print(
'Original size: ${(originalSize / 1024).toStringAsFixed(2)} KB');
print(
'Compressed size: ${(compressedSize / 1024).toStringAsFixed(2)} KB');
} else {
throw Exception('Compressed file not found at path: $targetPath');
}
} else {
throw Exception('Compression returned null path');
}
} catch (e) {
setState(() {
// Store error message.
_error = 'Error compressing image: $e';
// Stop compressing state.
_isCompressing = false;
});
print('Error compressing image: $e');
}
}
// This function builds the user interface.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GFG Image Compression'),
// Set app bar color.
backgroundColor: Colors.green,
// Set text color
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
// Add padding around the content.
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Button to pick and compress an image.
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, foregroundColor: Colors.white),
onPressed: _isCompressing
? null
: pickImage, // Disable when compressing.
child: Text(_isCompressing
? 'Compressing...' // Show progress message.
: 'Pick and Compress Image'),
),
if (_error != null) ...[
// Show error message if any.
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(8),
// Background color for error message.
color: Colors.red.shade100,
child: Text(
_error!,
// Red text color.
style: const TextStyle(color: Colors.red),
),
),
],
if (_selectedImage != null) ...[
// Show original image.
const SizedBox(height: 16),
const Text(
'Original Image',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Image.file(_selectedImage!),
],
if (_compressedImage != null) ...[
// Show compressed image.
const SizedBox(height: 16),
const Text(
'Compressed Image',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Image.file(_compressedImage!),
// Show original and compressed image sizes.
FutureBuilder<List<int>>(
future: Future.wait([
_selectedImage!.length(),
_compressedImage!.length(),
]),
builder: (context, snapshot) {
if (snapshot.hasData) {
final originalSize = snapshot.data![0] / 1024;
final compressedSize = snapshot.data![1] / 1024;
final savings =
((originalSize - compressedSize) / originalSize * 100);
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Original: ${originalSize.toStringAsFixed(2)} KB\n'
'Compressed: ${compressedSize.toStringAsFixed(2)} KB\n'
'Reduced by: ${savings.toStringAsFixed(1)}%',
style: const TextStyle(fontSize: 14),
),
);
}
// Return empty widget if no data.
return const SizedBox();
},
),
],
],
),
),
);
}
}
Output: