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
If your in a
BottomSheet
,setState
won’t work. You need to useStatefulBuilder
‘ssetState
instead:Wrap your sheet in it:
A sheet has its own Context. You’d want to pass in the function to be called from the parent Context. So:
where that onPress calls setState from the screen that calls
_showFullScreenBottomSheet