skip to Main Content

one thing you have to keep in mind. i have create this class in external file and executing sheet from main code because same sheet needed in many of screens. so for showing sheet perfectly first i have opened a sheet and then automatically appended sheet on that

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shimmer/shimmer.dart';
import 'package:iconsax/iconsax.dart';
import 'package:lottie/lottie.dart';
import '../../services/api_service.dart';
import '../../utils/shared_pref.dart';

class CommentBottomSheet extends StatefulWidget {
  final int postIndex;
  const CommentBottomSheet({super.key, required this.postIndex});

  @override
  CommentBottomSheetState createState() => CommentBottomSheetState();
}

class CommentBottomSheetState extends State<CommentBottomSheet> {
  bool isReplying = false;
  bool isLoading = true;
  List<dynamic> commentsData = [];
  final ApiService _apiService = ApiService();
  final ScrollController _scrollController = ScrollController();
  final TextEditingController _replyController = TextEditingController();
  final TextEditingController _commentController = TextEditingController();

  @override
  void initState(){
    super.initState();
    _fetchComments();
    WidgetsBinding.instance.addPostFrameCallback((_){
      _showFullScreenBottomSheet();
    });
  }

  @override
  void dispose(){
    _scrollController.dispose();
    _replyController.dispose();
    _commentController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context){
    return Scaffold(body: Container());
  }

  void _showFullScreenBottomSheet(){
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (BuildContext context) {
        return GestureDetector(
          onTap: () => FocusScope.of(context).unfocus(),
          child: Container(
            height: MediaQuery.of(context).size.height * 0.93,
            padding: const EdgeInsets.only(top: 12.0),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
              boxShadow: [
                BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10, spreadRadius: 5),
              ],
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Center(
                  child: Container(width: 50, height: 5, decoration: BoxDecoration(color: Colors.grey[300], borderRadius: BorderRadius.circular(5))),
                ),
                const SizedBox(height: 10),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text('Comments', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey[800])),
                      IconButton(
                        icon: Icon(Icons.close, color: Colors.grey[600]),
                        onPressed: (){
                          Navigator.pop(context);
                        },
                      ),
                    ],
                  ),
                ),
                Expanded(
                  child: isLoading ? _buildShimmerEffect() : commentsData.isEmpty ? _buildNoCommentsView() : SingleChildScrollView(
                    controller: _scrollController,
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: Column(children: commentsData.map((comment) => _buildComment(comment)).toList()),
                  ),
                ),
                Padding(
                  padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: MediaQuery.of(context).viewInsets.bottom + 16.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 16.0),
                          decoration: BoxDecoration(color: Colors.grey[100], borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.grey[300]!)),
                          child: TextField(
                            controller: isReplying ? _replyController : _commentController,
                            decoration: InputDecoration(
                              hintText: isReplying ? 'Write a reply...' : 'Write a comment...',
                              border: InputBorder.none,
                              hintStyle: TextStyle(color: Colors.grey[600]),
                              suffixIcon: isReplying ? IconButton(
                                icon: Icon(Icons.close, color: Colors.grey[600]),
                                onPressed: () {
                                  setState(() {
                                    isReplying = false;
                                    _replyController.clear();
                                  });
                                },
                              ) : null,
                            ),
                            onTap: () {
                              WidgetsBinding.instance.addPostFrameCallback((_) {
                                _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
                              });
                            },
                          ),
                        ),
                      ),
                      const SizedBox(width: 8.0),
                      GestureDetector(
                        onTap: () {
                          if (isReplying) {
                            print("Reply submitted: ${_replyController.text}");
                            setState(() {
                              isReplying = false;
                              _replyController.clear();
                            });
                          } else {
                            print("Comment submitted: ${_commentController.text}");
                            _commentController.clear();
                          }
                        },
                        child: Container(
                          padding: const EdgeInsets.all(12.0),
                          decoration: const BoxDecoration(color: Color(0xFF8E2DE2), shape: BoxShape.circle),
                          child: const Icon(Icons.send, color: Colors.white),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      },
    ).whenComplete(() {
      Navigator.of(context).pop();
    });
  }

  Widget _buildShimmerEffect() {
    return ListView.builder(
      itemCount: 5,
      itemBuilder: (context, index) {
        return Shimmer.fromColors(
          baseColor: Colors.grey[300]!,
          highlightColor: Colors.grey[100]!,
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                CircleAvatar(backgroundColor: Colors.grey[300]!, radius: 20),
                const SizedBox(width: 12.0),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Container(color: Colors.grey[300]!, height: 20, width: 100),
                      const SizedBox(height: 4.0),
                      Container(color: Colors.grey[300]!, height: 14, width: double.infinity),
                      const SizedBox(height: 8.0),
                      Container(color: Colors.grey[300]!, height: 14, width: 80),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Widget _buildNoCommentsView() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Lottie.asset('assets/lottie/no_chats_cards.json', width: 150, height: 150),
          const SizedBox(height: 20),
          Text('No comment found', style: TextStyle(fontSize: 16, color: Colors.grey[800])),
          const SizedBox(height: 10),
          Text('Be the first to comment on this post', style: TextStyle(fontSize: 14, color: Colors.grey[600])),
        ],
      ),
    );
  }

  Widget _buildComment(dynamic comment) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          CircleAvatar(
            backgroundImage: NetworkImage(comment['CommentUserData']['UserProfilePicture']),
            radius: 20,
          ),
          const SizedBox(width: 12.0),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('${comment['CommentUserData']['UserFirstName']} ${comment['CommentUserData']['UserLastName']}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                    Text(comment['UpdatedAtTimeAgo'], style: TextStyle(fontSize: 12, color: Colors.grey[600])),
                  ],
                ),
                const SizedBox(height: 4.0),
                Text(comment['CommentContent'], style: TextStyle(fontSize: 14, color: Colors.grey[800])),
                const SizedBox(height: 8.0),
                Row(
                  children: [
                    const LikeButton(),
                    const SizedBox(width: 16.0),
                    GestureDetector(
                      onTap: () {
                        setState(() {
                          isReplying = true;
                        });
                        WidgetsBinding.instance.addPostFrameCallback((_) {
                          _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
                        });
                      },
                      child: Row(
                        children: [
                          Icon(Icons.reply, color: Colors.grey[600], size: 18),
                          const SizedBox(width: 4.0),
                          Text('Reply', style: TextStyle(fontSize: 14, color: Colors.grey[600])),
                        ],
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _fetchComments() async {
    // -------- Call API for Like / Unlike -------- //
    var prefs = await SharedPreferences.getInstance();
    String? selfUserID = prefs.getString(CustomSharedConstants.userID);
    String? userSessionToken = prefs.getString(CustomSharedConstants.userSessionToken);

    try{
      var response = await _apiService.get(context, 'get-post-comments/$selfUserID/${widget.postIndex}', userSessionToken ?? "");
      int responseStatusCode = response['statusCode'];
      if(responseStatusCode == 200){
        List<dynamic> responseBody = response['body']["postData"];

        if(responseBody.isEmpty){
          if(mounted){
            setState((){
              isLoading = false;
              commentsData = [];
            });
          }
        }
        else{
          if (mounted) {
            setState(() {
              commentsData = responseBody;
              isLoading = false;
            });
          }
        }
      } else {
        // Handle non-200 status code here
        if (mounted) {
          setState(() {
            isLoading = false;
            commentsData = [];
          });
        }
      }
    } catch (e) {
      // Handle any errors here
      if (mounted) {
        setState(() {
          isLoading = false;
        });
      }
    }
  }

}

// --------- Like Button Class --------- //
class LikeButton extends StatefulWidget {
  const LikeButton({super.key});

  @override
  LikeButtonState createState() => LikeButtonState();
}

class LikeButtonState extends State<LikeButton> {
  bool isLiked = false;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(isLiked ? Iconsax.heart5 : Iconsax.heart, color: isLiked ? Colors.red : Colors.grey),
      onPressed: (){
        setState((){ isLiked = !isLiked; });
      },
    );
  }
}

I want to update this code that use almost on every click of screen.

"setState((){
  isLoading = false;
  commentsData = [];
});" 

it should reflect after update, it add data but not showing on screen until i click on screen.

2

Answers


  1. If your in a BottomSheet, setState won’t work. You need to use StatefulBuilder‘s setState instead:

    Wrap your sheet in it:

    showModalBottomSheet(
          context: context,
          isScrollControlled: true,
          backgroundColor: Colors.transparent,
          builder: (BuildContext context) {
            return StatefulBuilder( // -> Use this.
              builder: (BuildContext context, StateSetter setState) {
                return GestureDetector(
                  onTap: () => FocusScope.of(context).unfocus(),
                  child: Container(
    
    Login or Signup to reply.
  2. A sheet has its own Context. You’d want to pass in the function to be called from the parent Context. So:

    void _showFullScreenBottomSheet({required VoidCallback onPress})
    

    where that onPress calls setState from the screen that calls _showFullScreenBottomSheet

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search