Stream 内的 Flutter ListView.Builder 不更新
Posted
技术标签:
【中文标题】Stream 内的 Flutter ListView.Builder 不更新【英文标题】:Flutter ListView.Builder inside Stream does not update 【发布时间】:2020-10-25 08:22:02 【问题描述】:对不起,我的英语不好,我正在用颤振和 firebase 实时数据库做一个聊天应用程序。我正在尝试进行分页。首先,我加载最后 20 条聊天消息,当用户滚动到聊天顶部时,我想再加载 20 条消息。数据恢复正确,但列表视图没有更新。这是我的代码:
class _ChatState extends State<Chat>
TextEditingController messageEditingController = new TextEditingController();
ScrollController _scrollController = ScrollController();
bool _showKeyboard = true;
Stream _chats;
List item = [];
var _firebaseRef = FirebaseDatabase().reference().child('chatRoom');
sendMessage()
if (messageEditingController.text.isNotEmpty)
_firebaseRef.child(widget.chatRoomId).child("chats").push().set(
"sendBy": widget.user.uid,
"message": messageEditingController.text,
"time": DateTime.now().millisecondsSinceEpoch
);
setState(()
messageEditingController.text = "";
);
@override
void initState()
setState(()
_chats = _firebaseRef
.child(widget.chatRoomId)
.child("chats")
.limitToLast(20)
.onValue;
);
_scrollController.addListener(()
if (_scrollController.position.atEdge)
if (_scrollController.position.pixels == 0)
else
_requestMoreMessages();
);
super.initState();
_requestMoreMessages() async
List _moreItems =[];
_firebaseRef
.child(widget.chatRoomId)
.child("chats")
.orderByChild("time")
.endAt(item.elementAt(19)["time"])
.limitToLast(20)
.once()
.then((snapshot)
Map<dynamic, dynamic> map = snapshot.value;
map.forEach((key, data)
print(data["message"]);
_moreItems.add(
"key": key,
"message": data['message'],
"sendBy": data['sendBy'],
"time": data['time']
);
);
setState(()
item.addAll(_moreItems);
);
);
Widget chatMessages()
return StreamBuilder(
stream:_chats,
builder: (context, snapshot)
if (snapshot.hasData &&
!snapshot.hasError &&
snapshot.data.snapshot.value != null)
Map data = snapshot.data.snapshot.value;
item = [];
data.forEach((index, data) => item.add(
"key": index,
"message": data['message'],
"sendBy": data['sendBy'],
"time": data['time']
));
item.sort((b, a) => a["time"].compareTo(b["time"]));
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: item.length,
itemBuilder: (context, index)
return MessageTile(
message: item[index]['message'],
sendByMe: widget.user.uid == item[index]['sendBy'],
time: item[index]['time']);
);
else
return Container();
,
);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: appBarMain(context, widget.userName, widget.urlFoto, context),
body: Column(
children: <Widget>[
Flexible(
child: chatMessages(),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black26),
borderRadius: BorderRadius.circular(50),
color: Colors.black26),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: !_showKeyboard == true
? Icon(LineIcons.keyboard_o)
: Icon(LineIcons.smile_o),
onPressed: ()
if (MediaQuery.of(context).viewInsets.bottom ==
0)
SystemChannels.textInput
.invokeMethod('TextInput.show');
if (mounted)
setState(()
_showKeyboard = !_showKeyboard;
);
,
),
SizedBox(
width: 230,
child: TextField(
onTap: ()
if (mounted)
setState(()
_showKeyboard = true;
);
,
controller: messageEditingController,
style: simpleTextStyle(),
textInputAction: TextInputAction.none,
decoration: InputDecoration(
hintText: "Escribe un mensaje",
hintStyle: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
),
],
),
),
ClipOval(
child: Material(
color: Theme.of(context).primaryColorDark,
child: InkWell(
splashColor: Theme.of(context).accentColor,
child: IconButton(
color: Colors.white,
onPressed: ()
sendMessage();
// addMessage();
,
icon: Icon(LineIcons.paper_plane),
),
)),
),
],
),
),
)
],
));
【问题讨论】:
如果你想加载更多消息,我认为你应该使用 'startAfter' 而不是 'endAt'。 @konto211541 是云 Firestore 数据库的功能,但我使用的是实时数据库。我使用 'endAt' 获得了正确的数据,但我无法更新列表视图 @EduardoAndres 实时数据库和 Firestore 都有startAt
和 endAt
方法。我不确认您必须使用其他方法,但它确实存在于实时数据库 API 中。
【参考方案1】:
您正在获取数据并调用 setState(())
,这将更新 UI。这也意味着您的StreamBuilder
将被重建,并且您正在使用 item = [];
,这将在每次重建流构建器时清空列表。因此,您没有得到任何要显示的数据。
【讨论】:
感谢您的回答,我删除了“item = []”,现在我的 ListView 每次调用“_requestMoreMessages()”时都会复制初始数据,但不显示我正在检索的新数据从数据库中,我看不到我的错误:/ 您可以在 SteamBuilder 中移动item.addAll(_moreItems);
。这将做的是每次您的 StreammBuilder 重建时,它会将_moreItems
附加到项目列表中,这应该会有所帮助。【参考方案2】:
感谢 Shubham Gupta 的帮助,这是适合我的代码:
class _ChatState extends State<Chat>
TextEditingController messageEditingController = new TextEditingController();
ScrollController _scrollController = ScrollController();
int numeroDocumentos = 0;
bool _showKeyboard = true;
Stream _chats;
List item = [];
List _moreItems = [];
bool primeraVez = true;
var _firebaseRef = FirebaseDatabase().reference().child('chatRoom');
sendMessage()
if (messageEditingController.text.isNotEmpty)
_firebaseRef.child(widget.chatRoomId).child("chats").push().set(
"sendBy": widget.user.uid,
"message": messageEditingController.text,
"time": DateTime.now().millisecondsSinceEpoch
);
setState(()
messageEditingController.text = "";
);
@override
void initState()
item = [];
setState(()
_chats = _firebaseRef
.child(widget.chatRoomId)
.child("chats")
.limitToLast(20)
.onValue;
);
_scrollController.addListener(()
if (_scrollController.position.atEdge)
if (_scrollController.position.pixels == 0)
else
_requestMoreMessages();
);
super.initState();
_requestMoreMessages() async
_firebaseRef
.child(widget.chatRoomId)
.child("chats")
.orderByChild("time")
.endAt(item.elementAt(numeroDocumentos - 1)["time"] - 1)
.limitToLast(20)
.once()
.then((snapshot)
Map<dynamic, dynamic> map = snapshot.value;
map.forEach((key, data)
print(data["message"]);
_moreItems.add(
"key": key,
"message": data['message'],
"sendBy": data['sendBy'],
"time": data['time']
);
);
).then((value)
setState(() );
);
Widget chatMessages()
return StreamBuilder(
stream: _chats,
builder: (context, snapshot)
if (snapshot.hasData &&
!snapshot.hasError &&
snapshot.data.snapshot.value != null)
item = [];
Map data = snapshot.data.snapshot.value;
data.forEach((index, data) => item.add(
"key": index,
"message": data['message'],
"sendBy": data['sendBy'],
"time": data['time']
));
item = List.from(item)..addAll(_moreItems);
numeroDocumentos = item.length;
item.sort((b, a) => a["time"].compareTo(b["time"]));
return ListView.builder(
reverse: true,
controller: _scrollController,
itemCount: item.length,
itemBuilder: (context, index)
return MessageTile(
message: item[index]['message'],
sendByMe: widget.user.uid == item[index]['sendBy'],
time: item[index]['time']);
);
else
return Container();
,
);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: appBarMain(context, widget.userName, widget.urlFoto, context),
body: Column(
children: <Widget>[
Flexible(
child: chatMessages(),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black26),
borderRadius: BorderRadius.circular(50),
color: Colors.black26),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: !_showKeyboard == true
? Icon(LineIcons.keyboard_o)
: Icon(LineIcons.smile_o),
onPressed: ()
if (MediaQuery
.of(context)
.viewInsets
.bottom ==
0)
SystemChannels.textInput
.invokeMethod('TextInput.show');
if (mounted)
setState(()
_showKeyboard = !_showKeyboard;
);
,
),
SizedBox(
width: 230,
child: TextField(
onTap: ()
if (mounted)
setState(()
_showKeyboard = true;
);
,
controller: messageEditingController,
style: simpleTextStyle(),
textInputAction: TextInputAction.none,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
hintText: "Escribe un mensaje",
hintStyle: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
),
],
),
),
ClipOval(
child: Material(
color: Theme
.of(context)
.primaryColorDark,
child: InkWell(
splashColor: Theme
.of(context)
.accentColor,
child: IconButton(
color: Colors.white,
onPressed: ()
sendMessage();
// addMessage();
,
icon: Icon(LineIcons.paper_plane),
),
)),
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: _showKeyboard == false ? buildSticker() : Container(),
),
],
));
Widget buildSticker()
SystemChannels.textInput.invokeMethod('TextInput.hide');
return EmojiPicker(
rows: 4,
columns: 8,
buttonMode: ButtonMode.MATERIAL,
numRecommended: 21,
onEmojiSelected: (emoji, category)
messageEditingController.text =
messageEditingController.text + emoji.emoji;
messageEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: messageEditingController.text.length));
,
);
【讨论】:
以上是关于Stream 内的 Flutter ListView.Builder 不更新的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 专题82 初识 Flutter Stream #yyds干货盘点#