I am building a chat app with firebase and I am currently storing each message as a document inside a collection in firebase. I use a StreamBuilder to get the latest messages and display them. I want to add an animation when a new message is received and sent. I have tried using an Animatedlist, however, I don't get how to make it work with a StreamBuilder. As far as I understand I would have to call the insertItem function each time a new message is added. Is there a smarter way to do it? Or how would this be implemented?
This is what I have so far:
class Message {
final String uid;
final String message;
final Timestamp timestamp;
Message({this.uid, this.timestamp, this.message});
}
class MessagesWidget extends StatefulWidget {
final String receiver;
MessagesWidget({@required this.receiver});
@override
_MessagesWidgetState createState() => _MessagesWidgetState();
}
class _MessagesWidgetState extends State<MessagesWidget>{
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
Tween<Offset> _offset = Tween(begin: Offset(1,0), end: Offset(0,0));
@override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: StreamBuilder<List<Message>>(
stream: DatabaseService(uid: user.uid).getMessages(widget.receiver),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Loading();
default:
final messages = snapshot.data;
return messages.isEmpty
? SayHi(userID: widget.receiver,)
: AnimatedList(
key: _listKey,
physics: BouncingScrollPhysics(),
reverse: true,
initialItemCount: messages.length,
itemBuilder: (context, index, animation) {
final message = messages[index];
return SlideTransition(
position: animation.drive(_offset),
child: MessageWidget(
message: message,
userID: widget.receiver,
isCurrentUser: message.uid == user.uid,
),
);
},
);
}
}),
),
SizedBox(
height: 10,
),
NewMessage(
receiver: widget.receiver,
)
],
),
);
}
}```
You can update your widget's State
to this below:
class _MessagesWidgetState extends State<MessagesWidget> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
Tween<Offset> _offset = Tween(begin: Offset(1, 0), end: Offset(0, 0));
Stream<List<Message>> stream;
List<Message> currentMessageList = [];
User user;
@override
void initState() {
super.initState();
user = Provider.of<User>(context, listen: false);
stream = DatabaseService(uid: user.uid).getMessages(widget.receiver);
stream.listen((newMessages) {
final List<Message> messageList = newMessages;
if (_listKey.currentState != null &&
_listKey.currentState.widget.initialItemCount < messageList.length) {
List<Message> updateList =
messageList.where((e) => !currentMessageList.contains(e)).toList();
for (var update in updateList) {
final int updateIndex = messageList.indexOf(update);
_listKey.currentState.insertItem(updateIndex);
}
}
currentMessageList = messageList;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: StreamBuilder<List<Message>>(
stream: stream,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Loading();
default:
final messages = snapshot.data;
return messages.isEmpty
? SayHi(
userID: widget.receiver,
)
: AnimatedList(
key: _listKey,
physics: BouncingScrollPhysics(),
reverse: true,
initialItemCount: messages.length,
itemBuilder: (context, index, animation) {
final message = messages[index];
return SlideTransition(
position: animation.drive(_offset),
child: MessageWidget(
message: message,
userID: widget.receiver,
isCurrentUser: message.uid == user.uid,
),
);
},
);
}
}),
),
SizedBox(
height: 10,
),
NewMessage(
receiver: widget.receiver,
)
],
),
);
}
}
Also, update your Message
class to the code below:
// Using the equatable package, remember to add it to your pubspec.yaml file
import 'package:equatable/equatable.dart';
class Message extends Equatable{
final String uid;
final String message;
final Timestamp timestamp;
Message({this.uid, this.timestamp, this.message});
@override
List<Object> get props => [uid, message, timestamp];
}
Explanation:
The State
code above does the following:
currentMessageList
outside the build methodcurrentMessageList
.AnimatedList
widget at the specific index updateIndex
.The Message
code above does the following:
==
operator and the object hashcode
to allow the check in this line: List<Message> updateList = messageList.where((e) => !currentMessageList.contains(e)).toList();
work as intended. [Without overriding these getters, the check would fail as two different Message
objects with the same values would not be equivalent].