How to Upload and Update Images in ImageKit with Flutter BLoC

Upload and Update Images in ImageKit with Flutter BLoC

Profile pictures are one of the most personal parts of any app—but handling uploads, updates, and deletions efficiently?

That’s a whole different story.

Today, we’re integrating ImageKit.io to make profile picture management seamless in Connect.

Instead of just storing an image URL, we’re now tracking image IDs—making it easier to update and delete images without leaving orphan files behind.

GitHub Link: https://github.com/maitry4/Connect/

Upload and Update Images in ImageKit with Flutter BLoC

Step 1: Add File ID In The User Profile

As we are working with imagekit and we can only delete images using their ids via APIs.

(This can be done the other way without holding this inside the database. But as of now let’s keep it this way only.)

We will need to add a file id in the Profile User

lib/features/profile/domain/entities/profile_user.dart

➡️Click To View The Modified Code
import 'package:connect/features/auth/domain/entities/app_user.dart';

class CProfileUser extends CAppUser {
  final String bio;
  final String profileImageUrl;
  final String? profileImageId; // Added fileId

  CProfileUser({
    ...
    this.profileImageId,
  });

  // Method to update profile user
  CProfileUser copyWith({String? newBio, String? newProfileImageUrl, String? newProfileImageId}) {
    return CProfileUser(
     ...
      profileImageId: newProfileImageId ?? profileImageId,
    );
  }

  // Convert profile user -> json
  @override
  Map<String, dynamic> toJson() {
    ...
      'profileImageId': profileImageId,
    };
  }

  factory CProfileUser.fromJson(Map<String, dynamic> jsonUser) {
    return CProfileUser(
     ...
      profileImageId: jsonUser['profileImageId'],
    );
  }
}


Step 2: Create an ImageKit Repository

Private API Key

Create lib/config/api_keys.dart

In here create a string variable image_kit_private_api add your private api key here.

In the gitignore file add this line at bottom:

lib/config/api_keys.dart

Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC

Repository For ImageKit Data Storage

Go to the lib/features/profile/data/

And create imagekit_repo.dart

➡️Click To View imagekit_repo.dart Code
import 'dart:convert';
import 'dart:io';
import 'package:connect/config/api_keys.dart';
import 'package:dio/dio.dart';

class ImageKitRepo {
  final Dio _dio = Dio();

  final String _uploadUrl = "https://upload.imagekit.io/api/v1/files/upload";
  final String _privateApiKey = image_kit_private_api;

  Future<Map<String, String>?> uploadImage(File file) async {
    try {
      FormData formData = FormData.fromMap({
        "file": await MultipartFile.fromFile(file.path),
        "fileName": file.path.split('/').last,
        "folder": "/user_profiles",
      });

      Response response = await _dio.post(
        _uploadUrl,
        data: formData,
        options: Options(
          headers: {
            "Authorization": "Basic ${base64Encode(utf8.encode("$_privateApiKey:"))}"
          },
        ),
      );

      if (response.statusCode == 200) {
        return {
          "url": response.data["url"],
          "fileId": response.data["fileId"],
        };
      }
    } catch (e) {
      print("ImageKit Upload Error: $e");
    }
    return null;
  }

  Future<void> deleteImage(String fileId) async {
    try {
      Response response = await _dio.delete(
        "https://api.imagekit.io/v1/files/$fileId",
        options: Options(
          headers: {
            "Authorization": "Basic ${base64Encode(utf8.encode("$_privateApiKey:"))}"
          },
        ),
      );

      if (response.statusCode == 204) {
        print("Image deleted successfully.");
      } else {
        throw Exception("Failed to delete image.");
      }
    } catch (e) {
      print("ImageKit Delete Error: $e");
    }
  }

  Future<String?> getFileId(String imageUrl) async {
    try {
      Response response = await _dio.get(
        "https://api.imagekit.io/v1/files/",
        queryParameters: {"searchQuery": imageUrl},
        options: Options(
          headers: {
            "Authorization": "Basic ${base64Encode(utf8.encode("$_privateApiKey:"))}"
          },
        ),
      );

      if (response.statusCode == 200) {
        List files = response.data;
        for (var file in files) {
          if (file["url"] == imageUrl) {
            return file["fileId"];
          }
        }
      }
    } catch (e) {
      print("Error fetching file ID: $e");
    }
    return null;
  }
}

➡️Click To View This Code’s Explanation

1. The Three Variables:

  • Dio _dio = Dio();
    • This initializes an instance of Dio, which is a powerful HTTP client in Dart used for making API requests.
  • String _uploadUrl = "https://upload.imagekit.io/api/v1/files/upload";
    • This holds the API endpoint for uploading images to ImageKit.
  • String _privateApiKey = image_kit_private_api;
    • This stores the private API key (imported from api_keys.dart) used for authentication when making requests to ImageKit.

2. uploadImage Method:

  • This method takes a File object as input and uploads it to ImageKit.
  • Steps:
    1. Creates FormData with the file, filename, and target folder (/user_profiles).
    2. Sends a POST request to _uploadUrl with authentication using Basic authorization.
    3. If successful (HTTP 200), it returns a map containing the image URL and file ID.
    4. If an error occurs, it prints an error message and returns null.

3. deleteImage Method:

  • This method deletes an image from ImageKit using its fileId.
  • Steps:
    1. Sends a DELETE request to the ImageKit API using the file ID.
    2. If successful (HTTP 204), it prints a success message.
    3. If unsuccessful, it throws an exception with a failure message.
    4. If an error occurs during the process, it prints an error message.

4. getFileId Method:

If no match is found or an error occurs, it prints an error message and returns null.

This method retrieves the fileId of an image using its URL.

Steps:

Sends a GET request to ImageKit with a search query for the image URL.

If successful (HTTP 200), it loops through the response list to find a matching URL.

Returns the corresponding fileId if found.


Step 3: Modify Profile Repo

We will need to add two new methods to the:

lib/features/profile/domain/repos/profile_repo.dart

import 'dart:io';
import 'package:connect/features/profile/domain/entities/profile_user.dart';

abstract class CProfileRepo {
  Future<CProfileUser?> fetchUserProfile(String uid);
  Future<void> updateProfile(CProfileUser updatedProfile);
  Future<String?> uploadProfileImage(File file, String uid);
  Future<void> deleteProfileImage(String imageUrl, String? imageId);
}

Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC Upload and Update Images in ImageKit with Flutter BLoC


Step 4: Modify Firebase Profile Repository

The modified code can be viewed at: https://github.com/maitry4/Connect/blob/main/lib/features/profile/data/firebase_profile_repo.dart

The modifications improve how profile images are handled in the system.

Previously, the profile only stored a profile image URL.

Now, the system also keeps track of an image ID, making it easier to manage images—especially for updates and deletions.

Additionally, new methods allow uploading and deleting profile images using ImageKit, an external image storage service.

➡️Click To View The Detailed Explanation

Changes in fetchUserProfile and updateProfile

What’s Added?

  • profileImageId is now fetched and stored.
  • Added error logging (print("Error fetching user profile: $e")).

Why?

  • profileImageId is necessary for managing images (deletion, updates, etc.).
  • Error logging helps diagnose problems during profile retrieval.

New Methods

uploadProfileImage(File file, String uid)

What it does:

  • Uploads an image to ImageKit.
  • Stores the uploaded image’s URL and ID in Firestore.
  • Returns the image URL.

Why?

  • Allows users to upload profile images efficiently while keeping track of them for later management.

deleteProfileImage(String imageUrl, String? imageId)

What it does:

  • Deletes an image from ImageKit using its ID.
  • Logs an error if no ID is found.

Why?

  • Ensures users can remove old profile pictures without leaving unused images stored.

Step 5: Modify The Profile cubit

The modified code can be viewed at: https://github.com/maitry4/Connect/blob/main/lib/features/profile/presentation/cubits/profile_cubit.dart

The overall purpose of the modifications in profile_cubit.dart is to enhance profile updates by adding image upload functionality, improving efficiency with image compression, and refining error handling.

Key Enhancements:

  1. Profile Picture Support 📸
    • Users can now update their profile image along with their bio.
    • New images are uploaded, and old images are deleted to free up storage.
  2. Image Compression for Efficiency 🚀
    • Converts images to WebP format to reduce file size before uploading.
    • Helps in faster uploads and better app performance.
  3. Better Error Handling & Validation 🛠️
    • Prevents redundant profile updates if no changes are made.
    • More detailed error messages make debugging easier.
➡️Click To View The Detailed Explanation

Profile Image Upload & Update Logic

  • The updateProfile method now accepts File? newProfileImage, allowing users to update their profile picture along with their bio.
  • If a new image is provided, it gets compressed and uploaded using profileRepo.uploadProfileImage.
  • If the upload is successful, the profileImageUrl is updated; otherwise, an error is emitted.

Image Compression Before Upload

  • Added _convertToWebP(File imageFile) method to compress images before uploading.
  • Uses FlutterImageCompress to reduce image size while maintaining quality (format: WebP, quality: 80).
  • Saves compressed images in a temporary directory for efficient handling.

Delete Previous Profile Image (if applicable)

  • If a new image is uploaded, the old image is deleted using profileRepo.deleteProfileImage, preventing unnecessary storage usage.

Error Handling Improvements

Prevents unnecessary profile updates when no changes are made by checking if newBio and newProfileImage are both null.

More specific error messages, such as "Failed to compress image" or "Error updating profile: $e", make debugging easier.


Leave a Reply