Connect Day 11: Fixing Bugs & Setting Up Post Features

Setting Up Post Features

Welcome back to Connect: Building a Photo-Sharing App 5 Steps at a Time! Today’s focus? Fixing a pesky image upload bug and laying the foundation for the posting feature.

If you’re building a Flutter app with Firebase, this guide will help you:
✅ Structure your post entity efficiently
✅ Implement repositories for Firebase and ImageKit
✅ Handle state management with BLoC and Cubits

By the end of this, you’ll have a solid post creation system in place—fully functional and scalable. Let’s dive in!

GitHub Link:

Setting Up Post Features

Setting Up Post Features Setting Up Post Features Setting Up Post Features Setting Up Post Features

Fixed A Bug

There were a few issues in the web version while uploading the image through the profile edit page.

Now it’s fixed.

The image was actually not being uploaded whenever I was on the web.

Now, that it’s modified you can view that code on GitHub(Click Here).

I’ll keep fixing such issues as I encounter them so that we do not have much to do on deployment. 😊

Let’s head over to the main feature of our app.

Posting images.

Setting Up Post Features Setting Up Post Features Setting Up Post Features Setting Up Post Features

Step 1: Setup Post Entity

First, let’s create a post entity with the copywith, to json and from json methods.

Just like we did with the previous features.

In lib/features/post/domain/entities create post.dart

➡️Click To View post.dart
import 'package:cloud_firestore/cloud_firestore.dart';

class CPost {
  final String id;
  final String userId;
  final String userName;
  final String text;
  final String imageUrl;
  final String? imageId;
  final DateTime timestamp;

  CPost({
    required this.id,
    required this.userId,
    required this.userName,
    required this.text,
    required this.imageUrl,
    required this.imageId,
    required this.timestamp,
  });

  CPost copyWith({String? imageUrl, String? imageId}) {
    return CPost(
      id: id,
      userId: userId,
      userName: userName,
      text: text,
      imageUrl: imageUrl ?? this.imageUrl,
      imageId: imageId ?? this.imageId,
      timestamp: timestamp
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id':id,
      'userId':userId,
      'userName':userName,
      'text':text,
      'imageUrl':imageUrl,
      'imageId':imageId,
      'timestamp':Timestamp.fromDate(timestamp)
    } ;
  }

  factory CPost.fromJson(Map<String,dynamic> json) {
    return CPost(
      id: json['id'],
      userId: json['userId'],
      userName: json['userName'],
      text: json['text'],
      imageUrl: json['imageUrl'],
      imageId: json['imageId'],
      timestamp: (json['timestamp'] as Timestamp).toDate(),
    );
  }
}

Step 2: Setup Post Repo

Now, let’s create post_repo.dart in lib/features/post/domain/repos

We will need 4 methods majorly:

  1. create post
  2. delete post
  3. fetch all the posts
  4. fetch posts by user
  5. upload post image
  6. delete post image
➡️Click To View post_repo.dart
import 'package:connect/features/post/domain/entities/post.dart';

abstract class CPostRepo {
  Future<List<CPost>> fetchAllPosts();
  Future<void> createPost(CPost post);
  Future<void> deletePost(String postId);
  Future<List<CPost>> fetchPostsByUserId(String userId);
  Future<String?> uploadPostImage(String pid, dynamic file);
  Future<void> deletePostImage(String imageUrl, String? imageId);
}

Setting Up Post Features Setting Up Post Features Setting Up Post Features Setting Up Post Features

We might later go for an update post method too.

Step 3: Post Feature’s Firebase Repo

Now, we will start implementing all these methods in our Firebase repository.

In the lib/features/post/data/ create a new file firebase_post_repo.dart

➡️Click To View an Explanation of firebase_post_repo.dart

This class, CFirebasePostRepo, is a repository that interacts with Firebase Firestore to manage posts.

It implements CPostRepo, meaning it provides actual implementations for storing, retrieving, and deleting posts from Firestore.

Let’s break down each part of the class in simple words:

1. Class Definition

  • This class is named CFirebasePostRepo.
  • It implements the CPostRepo interface, which means it must provide implementations for the methods defined in CPostRepo.

2. Firebase Firestore Instance

  • This creates an instance of Firebase Firestore, which is used to interact with the database.
  • This defines a reference to a collection named posts in Firestore, where all posts will be stored.

3. createPost Method

  • Purpose: Adds a new post to Firestore.
  • How it works:
    1. Converts the CPost object into a JSON format using post.toJson().
    2. Saves this data to Firestore under the document ID given by post.id.
    3. If something goes wrong, it throws an exception.

4. deletePost Method

  • Purpose: Deletes a post from Firestore.
  • How it works:
    1. Finds the post by its postId.
    2. Deletes the document from Firestore.

5. fetchAllPosts Method

  • Purpose: Retrieves all posts from Firestore.
  • How it works:
    1. Fetches all posts from Firestore, ordered by timestamp (latest first).
    2. Converts each document into a CPost object using CPost.fromJson().
    3. Returns a list of all posts.

6. fetchPostsByUserId Method

  • Purpose: Retrieves all posts made by a specific user.
  • How it works:
    1. Queries Firestore for posts where userId matches the given userId.
    2. Converts each document into a CPost object.
    3. Returns a list of posts belonging to that user.

Summary of Methods

MethodPurposeWhat it Does
createPost(CPost post)Adds a new postConverts post to JSON and saves it in Firestore
deletePost(String postId)Deletes a postFinds and deletes post by ID
fetchAllPosts()Gets all postsRetrieves all posts, sorted by timestamp
fetchPostsByUserId(String userId)Gets posts by userRetrieves all posts created by a specific user

This class serves as a bridge between your app and Firebase Firestore, making it easy to manage posts. Let me know if you need further clarification!

Step 4: Post Feature’s Imagekit Repo

We will need methods to support the Firebase Repository.

In the lib/features/post/data/ create a new file imagekit_post_repo.dart

It would be almost the same as the image kit repo of the profile feature.

➡️Click To View an Explanation of imagekit_post_repo.dart

This class, ImageKitRepo, handles image uploads, deletions, and retrievals using the ImageKit API. It uses the Dio HTTP client for making API requests. Let’s go step by step and explain each part in simple words.

1. Class Definition and Variables

  • Dio HTTP client is used for making requests to ImageKit.
  • _uploadUrl stores the API endpoint for uploading images.
  • _privateApiKey stores the private API key (imported from api_keys.dart) for authentication.

2. uploadImage Method (Uploads an Image)

  • Purpose: Uploads an image to ImageKit and returns the uploaded image’s URL and file ID.
  • Parameters:
    • file: The image to be uploaded (can be a File or Uint8List).
    • uid: A unique user ID, used to name the file.

How it Works

  • Converts the file into a MultipartFile:
    • If file is a File (local file on device), it reads it from the given path.
    • If file is a Uint8List (raw bytes, common in web apps), it converts the bytes into a file with a name like "post_<uid>.webp".
    • If the file type is invalid, it returns null.
  • Creates a form-data request with:
    • file: The actual image.
    • fileName: The image’s name (e.g., "post_123.webp").
    • folder: The destination folder in ImageKit (/user_posts).
  • Sends a POST request to ImageKit to upload the image.
  • The Authorization header contains the private API key, which is base64-encoded for security.

3. deleteImage Method (Deletes an Image)

  • Purpose: Deletes an image from ImageKit using its fileId.

How it Works

  • Sends a DELETE request to ImageKit using the image’s fileId.
  • The request is authenticated using the private API key.
  • If the response status is 204 (successful deletion), it prints a success message.
  • If deletion fails, it throws an exception.

4. getFileId Method (Finds Image File ID)

  • Purpose: Fetches the fileId of an image from ImageKit using its URL.

How it Works

  • Sends a GET request to ImageKit’s API, searching for an image by its URL.
  • The request includes the API key for authentication.
  • If the response is successful:
    • It loops through the returned files and checks if the URL matches.
    • If a match is found, it returns the file ID.
  • If an error occurs, it prints an error message and returns null.

Summary of Methods

MethodPurposeWhat it Does
uploadImage(dynamic file, String uid)Uploads an imageConverts file, sends request, returns image URL & file ID
deleteImage(String fileId)Deletes an imageSends request to delete image by file ID
getFileId(String imageUrl)Finds image file IDSearches for image in ImageKit and returns file ID

Setting Up Post Features Setting Up Post Features Setting Up Post Features Setting Up Post Features

Step 5: Post States And Post Cubit

In the lib/features/post/presentation/cubits create a new file post_states.dart

➡️Click To View post_states.dart
import 'package:connect/features/post/domain/entities/post.dart';

abstract class CPostState {}

// initial
class CPostsInitialState extends CPostState {}

// loading...
class CPostsLoadingState extends CPostState {}

// uploading
class CPostsUploadingState extends CPostState {}

// error
class CPostsErrorState extends CPostState {
  final String message;
  CPostsErrorState(this.message);
}

// loaded
class CPostsLoadedState extends CPostState {
  final List<CPost> posts;
  CPostsLoadedState(this.posts);
}
class CPostLoadedState extends CPostState {}

In the lib/features/post/presentation/cubits create a new file post_cubit.dart

➡️Click To View an Explanation of post_cubit.dart

This CPostCubit class is responsible for managing posts in a Flutter app using the BLoC state management pattern.

It connects with Firebase Firestore and ImageKit to handle post creation, deletion, and fetching.

It also includes image compression methods for both mobile and web to optimize uploads.

1. Class Definition and Variables

  • CPostCubit extends Cubit<CPostState>, which means it manages post-related state in the app.
  • postRepo: Instance of CFirebasePostRepo, which handles Firestore interactions.
  • imageKitPostRepo: Instance of ImageKitPostRepo, which manages ImageKit uploads/deletions.
  • Initial State: The cubit starts with CPostsInitialState().

2. createPost Method (Creates a New Post)

  • Purpose: Handles the creation of a new post, including optional image uploading.
  • Parameters:
    • post: The post data.
    • newPostImage: A file image (for mobile).
    • webPostImage: A Uint8List image (for web).
  • State Change: It first emits CPostsLoadingState() to show that an operation is in progress.

Handling Image Uploads

  • If there’s an image (newPostImage is not null), it compresses it using _convertToWebP(), then uploads it to ImageKit.
  • After uploading, it retrieves:
    • imageUrl → the uploaded image’s URL.
    • imageId → ImageKit’s unique file ID.
  • If the image is for web, it gets compressed using _convertUint8ListToWebP() before being uploaded.

Saving the Post to Firestore

  • Creates a new post with the uploaded image URL and ID.
  • Saves it in Firestore using postRepo.createPost(newPost).
  • Emits CPostLoadedState() indicating success.

Error Handling

  • If something goes wrong, it emits CPostsErrorState() with the error message.

3. deletePost Method (Deletes a Post)

  • Finds the post by postId from the list of all posts.
  • Emits CPostsLoadingState() to indicate a loading state.

Deleting Image (If Exists)

  • If the post has an image, it deletes it from ImageKit.

Deleting the Post

  • Calls postRepo.deletePost(postId) to remove it from Firestore.
  • Emits CPostLoadedState() after successful deletion.

Error Handling

  • Emits CPostsErrorState() if an error occurs.

4. fetchAllPosts Method (Retrieves All Posts)

  • Fetches all posts from Firestore via postRepo.fetchAllPosts().
  • Emits:
    • CPostsLoadingState() → while fetching.
    • CPostsLoadedState(posts) → when data is successfully loaded.
    • CPostsErrorState(e.toString()) → if an error occurs.

5. _convertToWebP (Mobile Image Compression)

  • Purpose: Compresses and converts images to WebP format (for mobile) to reduce size.
  • Uses FlutterImageCompress to:
    • Save the compressed image in a temporary directory.
    • Maintain 80% quality.
    • Return the compressed File.
  • If compression fails, it prints an error and returns null.

6. _convertUint8ListToWebP (Web Image Compression)

  • Purpose: Compresses images for web (Uint8List format).
  • Uses image package to:
    • Decode the image.
    • Convert it to JPEG (70% quality).
    • Return the compressed Uint8List.
  • If compression fails, it returns the original image.

Summary of Methods

MethodPurposeWhat it Does
createPost(CPost post, {File? newPostImage, Uint8List? webPostImage})Creates a postUploads an image (if any), saves post to Firestore
deletePost(String postId)Deletes a postDeletes the image (if any), then removes post from Firestore
fetchAllPosts()Fetches all postsRetrieves posts from Firestore and updates state
_convertToWebP(File imageFile)Compresses mobile imagesConverts image to WebP (80% quality)
_convertUint8ListToWebP(Uint8List uint8list)Compresses web imagesConverts image to JPEG (70% quality)

Setting Up Post Features Setting Up Post Features Setting Up Post Features Setting Up Post Features

Conclusion

With our post entity, repository, and state management in place, we’re inching closer to a fully functional photo-sharing experience.

Tomorrow we will implement better error handling throughout our app and fix any issues remaining.

We will also have a brief recap of our app so far.

Then we will make the responsive UI for the posting feature functional.

What do you think so far? Have any insights or improvements? Drop a comment—I’d love to hear your thoughts!

Leave a Reply