I am building a chat app that stores conversation between users on Firestore. The app is working as expected in regards to send and receiving messages, but I am struggling with the stream being rebuild everytime I use the TextField and keyboard appears.
Here is what my code looks like
class ChatScreen extends StatefulWidget {
final String receiverID;
final String receiverEmail;
const ChatScreen(
{super.key,
this.receiverEmail = TTrialBot.botEmail,
this.receiverID = TTrialBot.botUserId});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
/// -- Text Controller
final TextEditingController _messageController = TextEditingController();
/// -- Chat & Auth Services
final controller = UserController.instance;
final ChatbotController _chatService = ChatbotController();
final AgentAPIQuery _apiQuery = AgentAPIQuery();
/// -- TextField Focus
final FocusNode myfocusNode = FocusNode();
@override
void initState() {
super.initState();
/// -- Add Listener to focus node
myfocusNode.addListener(() {
if (myfocusNode.hasFocus) {
Future.delayed(
const Duration(milliseconds: 500),
() => scrollDown(),
);
}
});
Future.delayed(
const Duration(milliseconds: 500),
() => scrollDown(),
);
}
@override
void dispose() {
myfocusNode.dispose();
_messageController.dispose();
super.dispose();
}
/// -- Scroll Controller
final ScrollController _scrollController = ScrollController();
void scrollDown() {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent + 20000,
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
);
}
});
}
/// -- Send Message
void sendMessage() async {
/// -- Get current User Info
final String? currentUserID = controller.user.value.id;
final String currentUserEmail = controller.user.value.email;
// if there is something inside the text field
if (_messageController.text.isNotEmpty) {
var prompt = _messageController.text;
// send message
await _chatService.sendMessage(
widget.receiverID, prompt, currentUserID, currentUserEmail);
// Clear text Controller
_messageController.clear();
// Send Prompt
await _apiQuery.promptRequest(prompt, currentUserID);
}
scrollDown();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//backgroundColor: TColors.secondary,
bottomNavigationBar: _chatTextField(context),
body: Column(
children: [
TPrimaryHeaderContainer(
child: Column(
children: [
///TAppBar(showBackArrow: true, title: Text('Account',style: Theme.of(context).textTheme.headlineMedium!.apply(color: TColors.white),)),
const SizedBox(height: TSizes.spaceBtwSections * 1.5),
/// User Profile Card
TChatProfileTile(
iconImage: FirebaseAuth.instance.currentUser != null
? Iconsax.user
: Iconsax.notification5,
onPressed: FirebaseAuth.instance.currentUser != null
? () => Get.to(() => const SettingsScreen())
: () => Get.to(
() => const CreateAccountNotificationScreen())),
const SizedBox(height: TSizes.spaceBtwSections),
],
)),
//const SizedBox(height: 5),
Expanded(
child: SizedBox(
height: double.infinity,
child: _buildMessageList(),
))
],
),
);
}
Widget _buildMessageList() {
String? senderID = controller.user.value.id;
return StreamBuilder(
stream: _chatService.getMessages(widget.receiverID, senderID),
builder: (context, snapshot) {
// errors
if (snapshot.hasError) {
return const Text('Error');
}
// Loading...
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('Loading');
}
return ListView.builder(
itemCount: 5,
shrinkWrap: true,
controller: _scrollController,
itemBuilder: (BuildContext context, int index) {
return Column(
children: snapshot.data!.docs
.map((doc) => _buildMessageItem(doc))
.toList(),
);
},
);
});
}
Widget _buildMessageItem(DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
// Check message owner
bool isCurrentUser = data['senderID'] == controller.user.value.id!;
// Align message to the right of the Current User
var alignment =
isCurrentUser ? Alignment.centerRight : Alignment.centerLeft;
return Container(
alignment: alignment,
child: Column(
crossAxisAlignment:
isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
ChatBuble(
message: data['message'],
isCurrentUser: isCurrentUser,
)
],
));
}
Widget _chatTextField(context) {
return Container(
//color: TColors.dark,
padding: EdgeInsets.only(
left: 10,
right: 10,
top: 5,
bottom: MediaQuery.of(context).viewInsets.bottom + 5),
child: Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 18),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30), color: Colors.grey),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: const InputDecoration(
//border: InputBorder.none,
hintText: TTexts.chatmessage,
hintStyle: TextStyle(
fontSize: TSizes.fontSizeMd,
fontWeight: FontWeight.w400,
color: TColors.white)),
focusNode: myfocusNode,
)),
CircleAvatar(
backgroundColor: TColors.primary,
child: IconButton(
onPressed: sendMessage,
icon: const Icon(Icons.send),
color: Colors.white,
),
)
],
),
),
);
}
}
I have look into other responses on StackOverflow, but I wasn’t able to figure out.
Flutter Switching to Tab Reloads Widgets and runs FutureBuilder
Using TextField inside a Streambuilder