Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

🚀 Planning the Revamp of Connect

We’re giving Connect a fresh identity! Instead of being just another social app, Connect will now be a photo progress-sharing platform.

Why the Change?

I find that regular social apps already fulfill their purpose. What I plan to do is create a space to document my journey in an *aesthetic* and structured way.

So, whether you’re:
🎨 Painting, 💻 Coding, 📚 Studying, or 🏋️‍♂️ Working out—Connect will help you visually track your progress, stay accountable, and get inspired by others.

New Features Coming Soon:

Collections – Categorize your photos into different progress journals
🔒 Privacy Controls – Make posts public or private (private posts are only visible to followers)
🧭 Better Navigation – Smoother UI & UX for an intuitive experience

🚀 Here’s a sneak peek at what’s changing:

connect new version sneak peek
Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

Have any suggestions? Drop them in the comments! I’d love to hear your thoughts.

🐞 Step 1: Bug Fixes & Log Out Button

Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

1️⃣ Fixing the Like Feature

I made a silly mistake by calling fetchallposts in the UI components of posts.

Remove that.

Also, the like feature was causing bugs.

  • The first thing I forgot to mention was that add likes field to all the existing posts in firebase database manually.
  • Then modify the copywith method as we will be toggling likes also modify fromjson to handle empty likes.
//copywith
likes: likes ?? this.likes,
//fromjson
likes: List<String>.from(json['likes'] ?? []),

The likes feature should work fine after this.

2️⃣ Adding the Log Out Button

To allow users to log out, just add this to the sidebar:

SidebarXItem(
          icon: Icons.logout,
          label: 'Log Out',
          onTap: () {
            context.read<CAuthCubit>().logout();
          },
        )

✅ Test it on both web and mobile to make sure it works!

💬 Step 2: Creating the Comment Entity

Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

Instead of adding comments directly inside the Post Entity, we’ll create a separate Comment Entity to keep things clean.

Comment Entity Fields:

🆔 id – Unique comment ID
📌 postId – ID of the post the comment belongs to
👤 userId – ID of the user who commented
✏️ commentText – The actual comment
timestamp – When the comment was posted

➡️Click To View The Comment Entity
import 'package:cloud_firestore/cloud_firestore.dart';

class CComment {
  final String id;
  final String postId;
  final String userId;
  final String userName;
  final String text;
  final DateTime timestamp;

  CComment({
    required this.id,
    required this.postId,
    required this.userId,
    required this.userName,
    required this.text,
    required this.timestamp,
  });

  // comment to json
  Map<String,dynamic> toJson() {
    return {
      'id':id,
      'postId': postId,
      'userId': userId,
      'userName': userName,
      'text':text,
      'timestamp': Timestamp.fromDate(timestamp),
    };
  }

  // json to comment
  factory CComment.fromJson(Map<String, dynamic> json) {
    return CComment(
      id: json['id'], 
      postId: json['postId'], 
      userId: json['userId'], 
      userName: json['userName'], 
      text: json['text'], 
      timestamp: (json['timestamp'] as Timestamp).toDate()
    );
  }
}

📝 Step 3: Adding Comments to the Post Entity

Now that we have a Comment Entity, let’s modify the Post Entity to hold comments.

1️⃣ Add an empty comments list to all existing posts in Firebase.

2️⃣ Modify the Post Entity to include comments

3️⃣ Update the constructor, copyWith, toJson, and fromJson methods to handle comments correctly

4️⃣ Update fromJson to convert Firebase’s list of maps into a list of Comment objects

CPost copyWith({String? imageUrl, String? imageId, List<String>? likes, List<CComment>? comments}) {
    return CPost(
      id: id,
      userId: userId,
      userName: userName,
      text: text,
      imageUrl: imageUrl ?? this.imageUrl,
      imageId: imageId ?? this.imageId,
      timestamp: timestamp,
      likes: likes ?? this.likes,
      comments: comments?? this.comments,
    );
  }

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

  factory CPost.fromJson(Map<String,dynamic> json) {
    final List<CComment> comments = (json['comments'] as List<dynamic>?)?.map((commentJson)=> CComment.fromJson(commentJson)).toList()??[];
    
    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(),
      likes: List<String>.from(json['likes'] ?? []),
      comments: comments,
    );
  }

Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs! Bringing Conversations to Connect: Implementing Comments & Fixing Bugs!

5️⃣ Modify the Create Post Page to ensure all new posts are created with an empty list of comments

comments: [],

Test if everything loads properly without producing any errors.

🔥 Step 4: Adding Comment Functionality in Firebase Repo

Time to make comments work!

We’ll add two new methods in the Post Repo:

1️⃣ addComment(postId, comment) – Fetch the post, add the new comment, and update Firestore.
2️⃣ deleteComment(postId, commentId) – Remove the selected comment from Firestore.

Now let’s implement these methods in Firebase post repo.

@override
  Future<void> addComment(String postId, CComment comment) async {
    try {
      // get the post doc
      final postDoc = await postsCollection.doc(postId).get();

      if(postDoc.exists) {
        // json to object
        final post = CPost.fromJson(postDoc.data() as Map<String, dynamic>);

        // add the comment
        post.comments.add(comment);

        // update in firebase
        await postsCollection.doc(postId).update({
          'comments': post.comments.map((comment) => comment.toJson()).toList()
        });
      }
      else {
        throw Exception("Post not Found");
      }
    }
    catch (e) {
      throw Exception("Error adding comment: $e");
    }
  }

  @override
  Future<void> deleteComment(String postId, String commentId) async {
    try {
      // get the post doc
      final postDoc = await postsCollection.doc(postId).get();

      if(postDoc.exists) {
        // json to object
        final post = CPost.fromJson(postDoc.data() as Map<String, dynamic>);

        // add the comment
        post.comments.removeWhere((comment) => comment.id == commentId);

        // update in firebase
        await postsCollection.doc(postId).update({
          'comments': post.comments.map((comment) => comment.toJson()).toList()
        });
      }
      else {
        throw Exception("Post not Found");
      }
    }
    catch (e) {
      throw Exception("Error deleting comment: $e");
    }
  }

Update Firestore: Add an empty comments array in all existing posts.
🔍 Test if adding and deleting comments works fine!

🖥️ Step 5: Building the Comment UI (Mobile & Desktop)

Now, let’s bring comments to life in the UI!

1️⃣ Post Cubit Updates:

  • Add methods to add and delete comments.
// add a comment to the post
  Future<void> addComment(String postId, CComment comment) async {
    try {
      await postRepo.addComment(postId, comment);
      await fetchAllPosts();
    }
    catch (e) {
      emit(CPostsErrorState("Failed to add comment: $e"));
    }
  }

  // delete a comment from the post
  Future<void> deleteComment(String postId, String commentId) async {
    try {
      await postRepo.deleteComment(postId, commentId);
      await fetchAllPosts();
    }
    catch (e) {
      emit(CPostsErrorState("Failed to delete comment: $e"));
    }
  }

2️⃣ Implement Comment UI:

  • Open a dialog box when the user clicks on the comment icon.
  • Take the comment input and send it to the cubit’s method.
  • Dynamically update the comment count.
➡️Click To View The Methods And Variables
late String currentUserId;
  late String ccurrentUserId;
  late String currentUserName;
  late final CPostCubit postCubit;
  final commentController = TextEditingController();

  @override
  void initState() {
    super.initState();
    // Get current user's ID from Firebase Auth
    currentUserId = FirebaseAuth.instance.currentUser?.uid ?? "";
    postCubit = context.read<CPostCubit>();
    final authCubit = context.read<CAuthCubit>();
    final user = authCubit.currentUser;
    ccurrentUserId = user?.uid ?? "";
    currentUserName = user?.name ?? "Anonymous";
  }

  void openNewCommentBox() {
    showDialog(
        context: context,
        builder: (context) => AlertDialog(
              content: CBioInputField(
                bioController: commentController,
                text: "Type a Comment",
              ),
              actions: [
                // cancel
                TextButton(
                    child:  Text("Cancel", style: TextStyle(color: Theme.of(context).colorScheme.inversePrimary)),
                    onPressed: () => Navigator.of(context).pop()),
                // save
                TextButton(
                    onPressed: () {
                      addComment();
                      Navigator.of(context).pop();
                    },
                    child:  Text("Save", style: TextStyle(color: Theme.of(context).colorScheme.inversePrimary))),
              ],
            ));
  }

  void addComment() {
    // make a comment
    final newComment = CComment(
        id: DateTime.now().millisecondsSinceEpoch.toString(),
        postId: widget.post.id,
        userId: currentUserId,
        userName: currentUserName,
        text: commentController.text,
        timestamp: DateTime.now());

    // add comment using cubit
    if (commentController.text.isNotEmpty) {
      postCubit.addComment(widget.post.id, newComment);
    }
  }

Add a caption Section:

// add this below the existing post_desktop_ui 
// Caption:
Row(
  children: [
     Text(
      widget.post.userName,
      style: TextStyle(fontWeight: FontWeight.bold),
     ),
     SizedBox(
      width: 10,
     ),
     Text(widget.post.text)
   ],
),

Now let’s add a comment section:

➡️Click To View The Code
// comment section
          BlocBuilder<CPostCubit, CPostState>(builder: (context, state) {
          if (state is CPostsLoadedState) {
            final post = state.posts.firstWhere((p) => p.id == widget.post.id);
            if (post.comments.isNotEmpty) {
              int commentCount = post.comments.length;

              int showCommentCount = post.comments.length;
              if(showCommentCount >1) {
                showCommentCount = 1;
              } // Show only first 

              return Column(
                children: [
                  ListView.builder(
                    shrinkWrap: true,
                    physics: NeverScrollableScrollPhysics(),
                    itemCount: showCommentCount,
                    itemBuilder: (context, index) {
                      final comment = post.comments[index];
                       return CCommentTile(
                        comment: comment,
                        currentUserId: currentUserId,
                        postId: widget.post.id,
                      );
                    },
                  ),
                  if (commentCount > 1) 
                    TextButton(
                      onPressed: () => showAllCommentsDialog(post.comments),
                      child: Text("Read More", style: TextStyle(color: Theme.of(context).colorScheme.inversePrimary),),
                    ),
                ],
              );
            }
          }
          return SizedBox();
        }),
➡️Click To View Explanation

1️⃣ Using BlocBuilder to Fetch Comments Dynamically

BlocBuilder<CPostCubit, CPostState>(builder: (context, state) {
  • This BlocBuilder listens for changes in CPostCubit.
  • It rebuilds the comment section whenever there’s a change in the post’s state (e.g., when a new comment is added or deleted).

2️⃣ Getting the Current Post from the State

if (state is CPostsLoadedState) {
  final post = state.posts.firstWhere((p) => p.id == widget.post.id);
  • First, it checks if the posts have been successfully loaded (CPostsLoadedState).
  • It then finds the specific post matching widget.post.id from the list of posts.
  • This ensures the comment section displays the correct comments for the corresponding post.

3️⃣ Counting & Displaying Comments

if (post.comments.isNotEmpty) {
  int commentCount = post.comments.length;
  int showCommentCount = post.comments.length;
  if (showCommentCount > 1) {
    showCommentCount = 1;
  } // Show only first 
  • It checks if there are any comments. If not, it doesn’t display anything.
  • commentCount holds the total number of comments.
  • showCommentCount determines how many comments to initially display (only one comment for now).
  • If there are multiple comments, it restricts the default view to only one to keep the UI clean.

4️⃣ Displaying the First Comment

ListView.builder(
  shrinkWrap: true,
  physics: NeverScrollableScrollPhysics(),
  itemCount: showCommentCount,
  itemBuilder: (context, index) {
    final comment = post.comments[index];
    return CCommentTile(
      comment: comment,
      currentUserId: currentUserId,
      postId: widget.post.id,
    );
  },
),
  • Uses ListView.builder to dynamically generate the comment list.
  • shrinkWrap: true prevents unnecessary scrolling inside another scrollable widget.
  • NeverScrollableScrollPhysics() ensures that the list does not interfere with the main post scroll.
  • It loops through the available comments (only one is shown by default).
  • Each comment is passed to the CCommentTile component for rendering.

5️⃣ “Read More” Button for Additional Comments

if (commentCount > 1) 
  TextButton(
    onPressed: () => showAllCommentsDialog(post.comments),
    child: Text("Read More", style: TextStyle(color: Theme.of(context).colorScheme.inversePrimary)),
  ),

Clicking this button calls showAllCommentsDialog(post.comments), which likely opens a dialog box displaying all comments for the post.

If there are more than 1 comments, a “Read More” button appears.

Here’s the comment Tile:

import 'package:connect/features/post/domain/entities/comment.dart';
import 'package:connect/features/post/presentation/cubits/post_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CCommentTile extends StatelessWidget {
  final CComment comment;
  final String currentUserId; // Pass current user ID
  final String postId; // Post ID is needed to delete the comment

  const CCommentTile({
    super.key,
    required this.comment,
    required this.currentUserId,
    required this.postId,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Name
        Text(comment.userName, style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(width: 10),

        // Comment Text
        Text(comment.text),

        // Delete Button (only visible if the current user is the author)
        if (comment.userId == currentUserId)
          IconButton(
            icon: Icon(Icons.delete, color: Colors.red),
            onPressed: () {
              context.read<CPostCubit>().deleteComment(postId, comment.id);
            },
          ),
      ],
    );
  }
}

Do the same for the mobile layout.

👀 Testing: Check if comments show up properly!

Leave a Reply