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
Contents
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:
- create post
- delete post
- fetch all the posts
- fetch posts by user
- upload post image
- 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 inCPostRepo
.
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:
- Converts the
CPost
object into a JSON format usingpost.toJson()
. - Saves this data to Firestore under the document ID given by
post.id
. - If something goes wrong, it throws an exception.
- Converts the
4. deletePost
Method
- Purpose: Deletes a post from Firestore.
- How it works:
- Finds the post by its
postId
. - Deletes the document from Firestore.
- Finds the post by its
5. fetchAllPosts
Method
- Purpose: Retrieves all posts from Firestore.
- How it works:
- Fetches all posts from Firestore, ordered by timestamp (latest first).
- Converts each document into a
CPost
object usingCPost.fromJson()
. - Returns a list of all posts.
6. fetchPostsByUserId
Method
- Purpose: Retrieves all posts made by a specific user.
- How it works:
- Queries Firestore for posts where
userId
matches the givenuserId
. - Converts each document into a
CPost
object. - Returns a list of posts belonging to that user.
- Queries Firestore for posts where
Summary of Methods
Method | Purpose | What it Does |
---|---|---|
createPost(CPost post) | Adds a new post | Converts post to JSON and saves it in Firestore |
deletePost(String postId) | Deletes a post | Finds and deletes post by ID |
fetchAllPosts() | Gets all posts | Retrieves all posts, sorted by timestamp |
fetchPostsByUserId(String userId) | Gets posts by user | Retrieves 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
imagekit_post_repo
.dartThis 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 fromapi_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 aMultipartFile
:- 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
.
- If
- 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
Method | Purpose | What it Does |
---|---|---|
uploadImage(dynamic file, String uid) | Uploads an image | Converts file, sends request, returns image URL & file ID |
deleteImage(String fileId) | Deletes an image | Sends request to delete image by file ID |
getFileId(String imageUrl) | Finds image file ID | Searches 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
extendsCubit<CPostState>
, which means it manages post-related state in the app.postRepo
: Instance ofCFirebasePostRepo
, which handles Firestore interactions.imageKitPostRepo
: Instance ofImageKitPostRepo
, 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
Method | Purpose | What it Does |
---|---|---|
createPost(CPost post, {File? newPostImage, Uint8List? webPostImage}) | Creates a post | Uploads an image (if any), saves post to Firestore |
deletePost(String postId) | Deletes a post | Deletes the image (if any), then removes post from Firestore |
fetchAllPosts() | Fetches all posts | Retrieves posts from Firestore and updates state |
_convertToWebP(File imageFile) | Compresses mobile images | Converts image to WebP (80% quality) |
_convertUint8ListToWebP(Uint8List uint8list) | Compresses web images | Converts 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!