ListView.builder 的奇怪行为及其索引
Weird Behavior of ListView.builder and working with its index
我正在使用 ListView.builder
和 StreamBuilder 从 Firebase 获取用户消息。我已经为聊天的日期分离实现了自己的登录。通过它我在聊天之间生成自定义日期消息,以便不同日期的聊天有明确的分隔。但奇怪的是我来到了列表视图的这种行为:
我猜这是因为 Listview 重用了小部件,并且我在我的代码中使用了基于索引的逻辑来生成日期消息。
如果这个问题无法解决,请建议我另一种显示日期和单独聊天的方法,比如 whatsapp
这是代码:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chat')
.orderBy(
'createdAt',
descending: true,
) //by default it is in accending order.
.snapshots(), //with ordering by timestamps!
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: AdaptiveCircularProgressIndicator(),
);
}
final chatDocs = chatSnapshot.data.docs;
// print('CHATS_DATA : ${chatDocs.data()}');
for (int i = 0; i < chatDocs.length; i++) {
print(
'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
}
return ListView.builder(
// addSemanticIndexes: false,
// addAutomaticKeepAlives: false,
// addRepaintBoundaries: false,
// cacheExtent: 10,
reverse: true,
itemBuilder: (ctx, index) {
var putMessage;
bool putDate = false;
String currentDate;
var nextDate;
if (index == 0) {
datesMessage = getDateStr(chatDocs[index].data()['createdAt']);
currentDate = datesMessage;
} else {
currentDate = getDateStr(chatDocs[index].data()['createdAt']);
}
if (index != chatDocs.length - 1)
nextDate = getDateStr(chatDocs[index + 1].data()['createdAt']);
// print('for $index : $datesMessage $currentDate');
bool loadNew = (index == chatDocs.length - 1) ||
(index != chatDocs.length - 1 &&
chatDocs[index + 1].data()['userId'] !=
chatDocs[index].data()['userId']) ||
(currentDate != nextDate);
if (index != 0 && datesMessage != currentDate) {
putMessage = datesMessage;
datesMessage = currentDate;
putDate = true;
print('date changed.');
} else {
putDate = false;
}
return Column(
children: [
// if (index == chatDocs.length - 1) Text(currentDate),
if (loadNew)
SizedBox(
height: 15,
),
MessageBubble(
chatDocs[index].data()['createdAt'],
loadNew ? true : false,
chatDocs[index].data()['text'],
chatDocs[index].data()['username'],
chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
chatDocs[index].data()['userId'] ==
user.uid, //checks wheather the message is mine or the other user.
key: ValueKey(
chatDocs[index]
.id, //this will ensure that flutter will efficiently re render the UI !
),
),
if (putDate)
Container(
margin: EdgeInsets.only(top: 15),
padding: EdgeInsets.all(5),
child: Text(
putMessage,
style: TextStyle(
color: Colors.white,
),
),
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.circular(5),
),
),
],
);
},
itemCount: chatDocs.length,
);
},
);
[查看日期消息,它正在从 7 月 21 日更改为 7 月 19 日!]
<a href="/link/to/site">
<img src="https://media.giphy.com/media/C2EcGHt68LEW0QAqJP/giphy.gif" />
</a>
这是完整的 GIF:
- https://media.giphy.com/media/C2EcGHt68LEW0QAqJP/giphy.gif
抱歉,我不能在这里上传 GIF,因为它超过了大小限制:(
这是我从 Firebase 获取的数据 如果有人需要:
I/flutter (14440): data for index : 0
I/flutter (14440): on July 21, 2021 Kamini : Wow
I/flutter (14440): data for index : 1
I/flutter (14440): on July 21, 2021 Kamini : Hi
I/flutter (14440): data for index : 2
I/flutter (14440): on July 21, 2021 Aayush : Hiiii
I/flutter (14440): data for index : 3
I/flutter (14440): on July 21, 2021 Aayush : Hello
I/flutter (14440): data for index : 4
I/flutter (14440): on July 21, 2021 Aayush :
I/flutter (14440): data for index : 5
I/flutter (14440): on July 21, 2021 Aayush : Hellooooo
I/flutter (14440): data for index : 6
I/flutter (14440): on July 21, 2021 Kamini : Hi there
I/flutter (14440): data for index : 7
I/flutter (14440): on July 19, 2021 Harshal : Sorry to bother you
I/flutter (14440): data for index : 8
I/flutter (14440): on July 19, 2021 Harshal : Okay, I understand that.
I/flutter (14440): data for index : 9
I/flutter (14440): on July 19, 2021 Harshal : Do you mind confirming your billing address for me?
I/flutter (14440): data for index : 10
I/flutter (14440): on July 19, 2021 Aayush : Thanks so much for being patient! We’ll be with you soon.
I/flutter (14440): data for index : 11
I/flutter (14440): on July 19, 2021 Harshal : Yup
I/flutter (14440): data for index : 12
I/flutter (14440): on July 19, 2021 Aayush : Did you get it?
I/flutter (14440): data for index : 13
I/flutter (14440): on July 19, 2021 Aayush : Welcome
I/flutter (14440): data for index : 14
I/flutter (14440): on July 19, 2021 Harshal : Yeah, Perfect
I/flutter (14440): data for index : 15
I/flutter (14440): on July 19, 2021 Aayush : Got it?
I/flutter (14440): data for index : 16
I/flutter (14440): on July 19, 2021 Aayush : something like this...
I/flutter (14440): data for index : 17
I/flutter (14440): on July 19, 2021 Aayush : I appreciate you explaining that to me. I’m going to connect you to our AayushTheApple team. I’ll let them know what you’re reaching out about.
I/flutter (14440): data for index : 18
I/flutter (14440): on July 19, 2021 Harshal : I can see why you’d want that! I’m sorry, but it’s not something that we currently offer right now.
I/flutter (14440): data for index : 19
I/flutter (14440): on July 19, 2021 Harshal : I hear you. I’m sorry that you’re still having trouble with this. I’m going to talk to my team to see what else we can do here.
I/flutter (14440): data for index : 20
I/flutter (14440): on July 19, 2021 Aayush : Nobody is perfect. There will come a time where every single customer service representative has to apologize. Apologies are more valuable than credits or discounts. They’re an essential tool in your team’s toolkits.
I/flutter (14440): data for index : 21
I/flutter (14440): on July 19, 2021 Harshal : Do you mind if we start a co-browsing session so I can help you with this process?
I/flutter (14440): data for index : 22
I/flutter (14440): on July 19, 2021 Harshal : Sure, why not?
I/flutter (14440): data for index : 23
I/flutter (14440): on July 17, 2021 Aayush : I think it might be easier if we could co-browse to address your issue. We’ll need to access your screen. Is that alright?
您可以使用 ListView.separated() 来完成。
下面的逻辑可能有点混乱,但我已经尽力描述它了:)
请阅读代码中的注释。
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class Message {
final DateTime dateSent;
final String text;
Message({
required this.dateSent,
required this.text,
});
}
class SomePage extends StatelessWidget {
final separatorDateFormatter =
DateFormat('dd MMM yyyy'); // Date Formatter for the separator
Widget build(BuildContext context) {
// We will generate some data to work with as an example
final messages = <Message>[];
// Generate 10 days
for (var day = 1; day < 11; day++) {
final time = DateTime.now().subtract(Duration(days: day));
// With 3 messages for each day
for (var i = 1; i < 4; i++) {
final messageTime = time.subtract(Duration(hours: i));
messages.add(Message(
dateSent: messageTime,
text: 'Day $day, Message #$i, Time $messageTime',
));
}
}
return Scaffold(
backgroundColor: Colors.grey[400],
appBar: AppBar(
title: Text('ListView.separated'),
),
body: ListView.separated(
// We add +1 to the actual list size so that we can build the very first separator (conversation start date)
itemCount: messages.length + (messages.isNotEmpty ? 1 : 0),
// Your message widget here
itemBuilder: (context, index) {
if (index == 0) {
final firstMessage = messages.first;
return _buildDateSeparator(firstMessage.dateSent);
}
final message = messages.elementAt(index - 1);
return _messageWidget(message);
},
// This is the place to build the date separator
separatorBuilder: (context, index) {
// If index is 0, the return nothing (since we have added +1 to the count above)
if (index == 0) return SizedBox();
// We take current Message from the list (as usual, -1 because of increased length above)
final currentMessage = messages.elementAt(index - 1);
// We get the next message from the list
// No need to check if the message might be null, since **separatorBuilder()** will not be called after the last entry of the list
final nextMessage = messages.elementAt(index);
// Compare it's date to the current one
if (!_isSameDate(currentMessage, nextMessage)) {
// Return date separator if the date doesn't match
return _buildDateSeparator(nextMessage.dateSent);
}
// Return nothing otherwise
return SizedBox();
},
),
);
}
// The function that compares the date between two messages
// This code can be exported to some helper class or refactored in to an Extension for DateTime class
bool _isSameDate(Message message1, Message message2) =>
message1.dateSent.year == message2.dateSent.year &&
message1.dateSent.month == message2.dateSent.month &&
message1.dateSent.day == message2.dateSent.day;
Widget _messageWidget(Message message) => Container(
height: 40.0,
color: Colors.grey,
margin: EdgeInsets.symmetric(
vertical: 5.0,
),
child: Center(
child: Text(message.text),
),
);
Widget _buildDateSeparator(DateTime date) => Container(
child: Center(
child: Text(separatorDateFormatter.format(date)),
),
);
}
另请注意,ListView.separated() 不会在列表的第一项之前和之后调用 separatorBuilder()最后一个(仅在项目之间)。因此,为了显示初始日期分隔符(对话开始日期),我们必须将 itemCount 增加 1,并在我们想要 select 当前列表条目时将索引减少 1 .
我相信您是对的,这是由于 ListView.builder 重复使用小部件造成的。要解决这个问题,您可以给每个 dateSeparator 一个 GlobalObjectKey(date)。像这样:
Widget _buildDateSeparator(DateTime date) => Container(
child: Center(
key: GlobalObjectKey(date),
child: Text(separatorDateFormatter.format(date)),
),
);
我没有测试过,但我以前遇到过类似的问题。
感谢@Thepeanut,
应用他提供的逻辑后,
这是最终的工作代码:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chat')
.orderBy(
'createdAt',
descending: true,
) //by default it is in accending order.
.snapshots(), //with ordering by timestamps!
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: AdaptiveCircularProgressIndicator(false),
);
}
final chatDocs = chatSnapshot.data.docs;
// print('CHATS_DATA : ${chatDocs.data()}');
for (int i = 0; i < chatDocs.length; i++) {
print(
'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
}
return ListView.separated(
reverse: true,
itemBuilder: (context, index) {
// bool loadNew = false;
bool loadNew = (index == chatDocs.length - 1) ||
(index != chatDocs.length - 1 &&
chatDocs[index + 1].data()['userId'] !=
chatDocs[index].data()['userId']);
if (index == chatDocs.length - 1) {
var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
print(
'index end : itembuilder. DateSepator builded. || $dateMessage');
return Column(
children: [
_buildDateSeparator(dateMessage),
_buildMessage(
chatDocs: chatDocs,
loadNew: loadNew,
index: index,
user: user,
),
],
);
}
print(
'index $index : itembuilder. Message builded. || ${chatDocs[index].data()['text']}');
return _buildMessage(
chatDocs: chatDocs,
loadNew: loadNew,
index: index,
user: user,
);
},
separatorBuilder: (context, index) {
if (index == chatDocs.length - 1) {
print('\tno separator.');
return SizedBox();
}
final currentMessageDate =
chatDocs[index].data()['createdAt'].toDate();
// if (index == chatDocs.length - 1) {
// var dateMessage =
// getDateStr(chatDocs[index - 1].data()['createdAt']);
// return _buildDateSeparator(dateMessage);
// }
// print('accessing overflow : $index ${chatDocs.length}');
final nextMessageDate =
chatDocs[index + 1].data()['createdAt'].toDate();
if (!_isSameDate(currentMessageDate, nextMessageDate)) {
var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
print('\tDate separator. || $dateMessage');
return _buildDateSeparator(dateMessage);
}
print('\tno separator end.');
return SizedBox();
},
itemCount: chatDocs.length,
// itemCount: chatDocs.length + (chatDocs.isNotEmpty ? 1 : 0),
);
},
)
附加功能:
Widget _buildDateSeparator(String dateMessage) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
// key: GlobalObjectKey(putMessage),
margin: EdgeInsets.only(top: 15),
padding: EdgeInsets.all(5),
child: Text(
dateMessage,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
),
),
constraints: BoxConstraints(),
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.circular(5),
),
),
],
);
}
Widget _buildMessage({
@required var chatDocs,
@required bool loadNew,
@required int index,
@required var user,
}) {
return Column(
children: [
if (loadNew)
SizedBox(
height: 15,
),
MessageBubble(
chatDocs[index].data()['createdAt'],
loadNew ? true : false,
chatDocs[index].data()['text'],
chatDocs[index].data()['username'],
chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
chatDocs[index].data()['userId'] ==
user.uid, //checks wheather the message is mine or the other user.
key: ValueKey(
chatDocs[index]
.id, //this will ensure that flutter will efficiently re render the UI !
),
),
],
);
}
bool _isSameDate(DateTime date1, DateTime date2) {
return date1.year == date2.year &&
date1.month == date2.month &&
date1.day == date2.day;
}
参考他的逻辑和必要的评论!
我正在使用 ListView.builder
和 StreamBuilder 从 Firebase 获取用户消息。我已经为聊天的日期分离实现了自己的登录。通过它我在聊天之间生成自定义日期消息,以便不同日期的聊天有明确的分隔。但奇怪的是我来到了列表视图的这种行为:
我猜这是因为 Listview 重用了小部件,并且我在我的代码中使用了基于索引的逻辑来生成日期消息。 如果这个问题无法解决,请建议我另一种显示日期和单独聊天的方法,比如 whatsapp
这是代码:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chat')
.orderBy(
'createdAt',
descending: true,
) //by default it is in accending order.
.snapshots(), //with ordering by timestamps!
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: AdaptiveCircularProgressIndicator(),
);
}
final chatDocs = chatSnapshot.data.docs;
// print('CHATS_DATA : ${chatDocs.data()}');
for (int i = 0; i < chatDocs.length; i++) {
print(
'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
}
return ListView.builder(
// addSemanticIndexes: false,
// addAutomaticKeepAlives: false,
// addRepaintBoundaries: false,
// cacheExtent: 10,
reverse: true,
itemBuilder: (ctx, index) {
var putMessage;
bool putDate = false;
String currentDate;
var nextDate;
if (index == 0) {
datesMessage = getDateStr(chatDocs[index].data()['createdAt']);
currentDate = datesMessage;
} else {
currentDate = getDateStr(chatDocs[index].data()['createdAt']);
}
if (index != chatDocs.length - 1)
nextDate = getDateStr(chatDocs[index + 1].data()['createdAt']);
// print('for $index : $datesMessage $currentDate');
bool loadNew = (index == chatDocs.length - 1) ||
(index != chatDocs.length - 1 &&
chatDocs[index + 1].data()['userId'] !=
chatDocs[index].data()['userId']) ||
(currentDate != nextDate);
if (index != 0 && datesMessage != currentDate) {
putMessage = datesMessage;
datesMessage = currentDate;
putDate = true;
print('date changed.');
} else {
putDate = false;
}
return Column(
children: [
// if (index == chatDocs.length - 1) Text(currentDate),
if (loadNew)
SizedBox(
height: 15,
),
MessageBubble(
chatDocs[index].data()['createdAt'],
loadNew ? true : false,
chatDocs[index].data()['text'],
chatDocs[index].data()['username'],
chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
chatDocs[index].data()['userId'] ==
user.uid, //checks wheather the message is mine or the other user.
key: ValueKey(
chatDocs[index]
.id, //this will ensure that flutter will efficiently re render the UI !
),
),
if (putDate)
Container(
margin: EdgeInsets.only(top: 15),
padding: EdgeInsets.all(5),
child: Text(
putMessage,
style: TextStyle(
color: Colors.white,
),
),
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.circular(5),
),
),
],
);
},
itemCount: chatDocs.length,
);
},
);
[查看日期消息,它正在从 7 月 21 日更改为 7 月 19 日!]
<a href="/link/to/site">
<img src="https://media.giphy.com/media/C2EcGHt68LEW0QAqJP/giphy.gif" />
</a>
这是完整的 GIF:
- https://media.giphy.com/media/C2EcGHt68LEW0QAqJP/giphy.gif 抱歉,我不能在这里上传 GIF,因为它超过了大小限制:(
这是我从 Firebase 获取的数据 如果有人需要:
I/flutter (14440): data for index : 0
I/flutter (14440): on July 21, 2021 Kamini : Wow
I/flutter (14440): data for index : 1
I/flutter (14440): on July 21, 2021 Kamini : Hi
I/flutter (14440): data for index : 2
I/flutter (14440): on July 21, 2021 Aayush : Hiiii
I/flutter (14440): data for index : 3
I/flutter (14440): on July 21, 2021 Aayush : Hello
I/flutter (14440): data for index : 4
I/flutter (14440): on July 21, 2021 Aayush :
I/flutter (14440): data for index : 5
I/flutter (14440): on July 21, 2021 Aayush : Hellooooo
I/flutter (14440): data for index : 6
I/flutter (14440): on July 21, 2021 Kamini : Hi there
I/flutter (14440): data for index : 7
I/flutter (14440): on July 19, 2021 Harshal : Sorry to bother you
I/flutter (14440): data for index : 8
I/flutter (14440): on July 19, 2021 Harshal : Okay, I understand that.
I/flutter (14440): data for index : 9
I/flutter (14440): on July 19, 2021 Harshal : Do you mind confirming your billing address for me?
I/flutter (14440): data for index : 10
I/flutter (14440): on July 19, 2021 Aayush : Thanks so much for being patient! We’ll be with you soon.
I/flutter (14440): data for index : 11
I/flutter (14440): on July 19, 2021 Harshal : Yup
I/flutter (14440): data for index : 12
I/flutter (14440): on July 19, 2021 Aayush : Did you get it?
I/flutter (14440): data for index : 13
I/flutter (14440): on July 19, 2021 Aayush : Welcome
I/flutter (14440): data for index : 14
I/flutter (14440): on July 19, 2021 Harshal : Yeah, Perfect
I/flutter (14440): data for index : 15
I/flutter (14440): on July 19, 2021 Aayush : Got it?
I/flutter (14440): data for index : 16
I/flutter (14440): on July 19, 2021 Aayush : something like this...
I/flutter (14440): data for index : 17
I/flutter (14440): on July 19, 2021 Aayush : I appreciate you explaining that to me. I’m going to connect you to our AayushTheApple team. I’ll let them know what you’re reaching out about.
I/flutter (14440): data for index : 18
I/flutter (14440): on July 19, 2021 Harshal : I can see why you’d want that! I’m sorry, but it’s not something that we currently offer right now.
I/flutter (14440): data for index : 19
I/flutter (14440): on July 19, 2021 Harshal : I hear you. I’m sorry that you’re still having trouble with this. I’m going to talk to my team to see what else we can do here.
I/flutter (14440): data for index : 20
I/flutter (14440): on July 19, 2021 Aayush : Nobody is perfect. There will come a time where every single customer service representative has to apologize. Apologies are more valuable than credits or discounts. They’re an essential tool in your team’s toolkits.
I/flutter (14440): data for index : 21
I/flutter (14440): on July 19, 2021 Harshal : Do you mind if we start a co-browsing session so I can help you with this process?
I/flutter (14440): data for index : 22
I/flutter (14440): on July 19, 2021 Harshal : Sure, why not?
I/flutter (14440): data for index : 23
I/flutter (14440): on July 17, 2021 Aayush : I think it might be easier if we could co-browse to address your issue. We’ll need to access your screen. Is that alright?
您可以使用 ListView.separated() 来完成。
下面的逻辑可能有点混乱,但我已经尽力描述它了:) 请阅读代码中的注释。
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class Message {
final DateTime dateSent;
final String text;
Message({
required this.dateSent,
required this.text,
});
}
class SomePage extends StatelessWidget {
final separatorDateFormatter =
DateFormat('dd MMM yyyy'); // Date Formatter for the separator
Widget build(BuildContext context) {
// We will generate some data to work with as an example
final messages = <Message>[];
// Generate 10 days
for (var day = 1; day < 11; day++) {
final time = DateTime.now().subtract(Duration(days: day));
// With 3 messages for each day
for (var i = 1; i < 4; i++) {
final messageTime = time.subtract(Duration(hours: i));
messages.add(Message(
dateSent: messageTime,
text: 'Day $day, Message #$i, Time $messageTime',
));
}
}
return Scaffold(
backgroundColor: Colors.grey[400],
appBar: AppBar(
title: Text('ListView.separated'),
),
body: ListView.separated(
// We add +1 to the actual list size so that we can build the very first separator (conversation start date)
itemCount: messages.length + (messages.isNotEmpty ? 1 : 0),
// Your message widget here
itemBuilder: (context, index) {
if (index == 0) {
final firstMessage = messages.first;
return _buildDateSeparator(firstMessage.dateSent);
}
final message = messages.elementAt(index - 1);
return _messageWidget(message);
},
// This is the place to build the date separator
separatorBuilder: (context, index) {
// If index is 0, the return nothing (since we have added +1 to the count above)
if (index == 0) return SizedBox();
// We take current Message from the list (as usual, -1 because of increased length above)
final currentMessage = messages.elementAt(index - 1);
// We get the next message from the list
// No need to check if the message might be null, since **separatorBuilder()** will not be called after the last entry of the list
final nextMessage = messages.elementAt(index);
// Compare it's date to the current one
if (!_isSameDate(currentMessage, nextMessage)) {
// Return date separator if the date doesn't match
return _buildDateSeparator(nextMessage.dateSent);
}
// Return nothing otherwise
return SizedBox();
},
),
);
}
// The function that compares the date between two messages
// This code can be exported to some helper class or refactored in to an Extension for DateTime class
bool _isSameDate(Message message1, Message message2) =>
message1.dateSent.year == message2.dateSent.year &&
message1.dateSent.month == message2.dateSent.month &&
message1.dateSent.day == message2.dateSent.day;
Widget _messageWidget(Message message) => Container(
height: 40.0,
color: Colors.grey,
margin: EdgeInsets.symmetric(
vertical: 5.0,
),
child: Center(
child: Text(message.text),
),
);
Widget _buildDateSeparator(DateTime date) => Container(
child: Center(
child: Text(separatorDateFormatter.format(date)),
),
);
}
另请注意,ListView.separated() 不会在列表的第一项之前和之后调用 separatorBuilder()最后一个(仅在项目之间)。因此,为了显示初始日期分隔符(对话开始日期),我们必须将 itemCount 增加 1,并在我们想要 select 当前列表条目时将索引减少 1 .
我相信您是对的,这是由于 ListView.builder 重复使用小部件造成的。要解决这个问题,您可以给每个 dateSeparator 一个 GlobalObjectKey(date)。像这样:
Widget _buildDateSeparator(DateTime date) => Container(
child: Center(
key: GlobalObjectKey(date),
child: Text(separatorDateFormatter.format(date)),
),
);
我没有测试过,但我以前遇到过类似的问题。
感谢@Thepeanut, 应用他提供的逻辑后, 这是最终的工作代码:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chat')
.orderBy(
'createdAt',
descending: true,
) //by default it is in accending order.
.snapshots(), //with ordering by timestamps!
builder: (ctx, chatSnapshot) {
if (chatSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: AdaptiveCircularProgressIndicator(false),
);
}
final chatDocs = chatSnapshot.data.docs;
// print('CHATS_DATA : ${chatDocs.data()}');
for (int i = 0; i < chatDocs.length; i++) {
print(
'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
}
return ListView.separated(
reverse: true,
itemBuilder: (context, index) {
// bool loadNew = false;
bool loadNew = (index == chatDocs.length - 1) ||
(index != chatDocs.length - 1 &&
chatDocs[index + 1].data()['userId'] !=
chatDocs[index].data()['userId']);
if (index == chatDocs.length - 1) {
var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
print(
'index end : itembuilder. DateSepator builded. || $dateMessage');
return Column(
children: [
_buildDateSeparator(dateMessage),
_buildMessage(
chatDocs: chatDocs,
loadNew: loadNew,
index: index,
user: user,
),
],
);
}
print(
'index $index : itembuilder. Message builded. || ${chatDocs[index].data()['text']}');
return _buildMessage(
chatDocs: chatDocs,
loadNew: loadNew,
index: index,
user: user,
);
},
separatorBuilder: (context, index) {
if (index == chatDocs.length - 1) {
print('\tno separator.');
return SizedBox();
}
final currentMessageDate =
chatDocs[index].data()['createdAt'].toDate();
// if (index == chatDocs.length - 1) {
// var dateMessage =
// getDateStr(chatDocs[index - 1].data()['createdAt']);
// return _buildDateSeparator(dateMessage);
// }
// print('accessing overflow : $index ${chatDocs.length}');
final nextMessageDate =
chatDocs[index + 1].data()['createdAt'].toDate();
if (!_isSameDate(currentMessageDate, nextMessageDate)) {
var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
print('\tDate separator. || $dateMessage');
return _buildDateSeparator(dateMessage);
}
print('\tno separator end.');
return SizedBox();
},
itemCount: chatDocs.length,
// itemCount: chatDocs.length + (chatDocs.isNotEmpty ? 1 : 0),
);
},
)
附加功能:
Widget _buildDateSeparator(String dateMessage) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
// key: GlobalObjectKey(putMessage),
margin: EdgeInsets.only(top: 15),
padding: EdgeInsets.all(5),
child: Text(
dateMessage,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
),
),
constraints: BoxConstraints(),
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.circular(5),
),
),
],
);
}
Widget _buildMessage({
@required var chatDocs,
@required bool loadNew,
@required int index,
@required var user,
}) {
return Column(
children: [
if (loadNew)
SizedBox(
height: 15,
),
MessageBubble(
chatDocs[index].data()['createdAt'],
loadNew ? true : false,
chatDocs[index].data()['text'],
chatDocs[index].data()['username'],
chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
chatDocs[index].data()['userId'] ==
user.uid, //checks wheather the message is mine or the other user.
key: ValueKey(
chatDocs[index]
.id, //this will ensure that flutter will efficiently re render the UI !
),
),
],
);
}
bool _isSameDate(DateTime date1, DateTime date2) {
return date1.year == date2.year &&
date1.month == date2.month &&
date1.day == date2.day;
}
参考他的