setState 不会导致 UI 刷新
setState not causing UI to refresh
使用有状态的小部件,我有这个构建函数:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Icon(MdiIcons.atom),
Text("Message Channel")
],
)
),
body: compileWidget(context),
bottomSheet: Row(
children: [
Expanded(
child: DefaultTextFormField(true, null, hintText: "Message ...", controller: messageField)
),
IconButton(
icon: Icon(Icons.send),
onPressed: onSendPressed
)
],
),
);
}
正如您在正文中所见,有一个 compileWidget
函数定义为:
FutureBuilder<Widget> compileWidget(BuildContext context) {
return FutureBuilder(
future: createWidget(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
print("DONE loading widget ");
return snapshot.data;
} else {
return Container(
child: Center(
child: CircularProgressIndicator()
),
);
}
}
);
}
反过来,createWidget
函数如下所示:
List<Widget> bubbles;
Future<Widget> createWidget(BuildContext context) async {
await updateChatList();
// when working, return listview w/ children of bubbles
return Padding(
padding: EdgeInsets.all(10),
child: ListView(
children: this.bubbles,
),
);
}
// update chat list below
Future<void> updateChatList({Message message}) async {
if (this.bubbles != null) {
if (message != null) {
print("Pushing new notification into chat ...");
this.bubbles.add(bubbleFrom(message));
} else {
print("Update called, but nothing to do");
}
} else {
var initMessages = await Message.getMessagesBetween(this.widget.implicatedCnac.implicatedCid, this.widget.peerNac.peerCid);
print("Loaded ${initMessages.length} messages between ${this.widget.implicatedCnac.implicatedCid} and ${this.widget.peerNac.peerCid}");
// create init bubble list
this.bubbles = initMessages.map((e) {
print("Adding bubble $e");
return bubbleFrom(e);
}).toList();
print("Done loading bubbles ...");
}
}
聊天气泡井然有序地出现在屏幕上。但是,一旦收到新消息并被以下人员接收:
StreamSubscription<Message> listener;
@override
void initState() {
super.initState();
this.listener = Utils.broadcaster.stream.stream.listen((message) async {
print("{${this.widget.implicatedCnac.implicatedCid} <=> ${this.widget.peerNac.peerCid} streamer received possible message ...");
if (this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid) {
print("Message meant for this screen!");
await this.updateChatList(message: message);
setState(() {});
}
});
}
“针对此屏幕的消息!”打印,然后在 updateChatList
内打印“Pushing new notification into chat ...”,这意味着气泡已添加到数组中。最后,setState 被调用,但是额外的气泡没有被渲染。这里可能出了什么问题?
首先,让我解释一下 setState
是如何根据这个 link 工作的。
每当您更改 State
对象的内部状态时,请在传递给 setState
的函数 中进行更改。调用 setState
通知框架此对象的内部状态已更改,可能会影响此子树中的用户界面,这会导致框架为此 State
对象安排构建。
对于你的情况,我会说这只是一个惯例。您可以在有状态 class 中定义一个消息对象,并在侦听器和 setstate
中更新它,如下所示:
setState(() { this.message = message; });
注意:
任何更改之前,您需要检查这个问题
因为将 setstete
与 FutureBuilder
一起使用会在 StatefulWidget
中形成无限循环,从而降低性能和灵活性。也许,您应该改变设计屏幕的方法,因为 FutureBuilder 每次尝试获取数据时都会自动更新状态。
更新:
您的问题有更好的解决方案。因为您正在使用流来读取消息,所以您可以使用 StreamBuilder 而不是侦听器和 FutureBuilder
。它为提供数据流的服务带来更少的实现和更高的可靠性。每次 StreamBuilder
从 Stream 接收到一条消息,它会显示任何你想要的数据快照。
您需要确保 ListView 具有 UniqueKey
:
ScrollController _scrollController = ScrollController();
Stream<Widget> messageStream;
@override
void initState() {
super.initState();
// use RxDart to merge 2 streams into one stream
this.messageStream = Rx.merge<Message>([Utils.broadcaster.stream.stream.where((message) => this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid), this.initStream()]).map((message) {
print("[MERGE] stream recv $message");
this.widget.bubbles.add(bubbleFrom(message));
// this line below to ensure that, as new items get added, the listview scrolls to the bottom
this._scrollController = _scrollController.hasClients ? ScrollController(initialScrollOffset: _scrollController.position.maxScrollExtent) : ScrollController();
return ListView(
key: UniqueKey(),
controller: this._scrollController,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
padding: EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 50),
children: this.widget.bubbles,
);
});
}
注意:根据 Erfan 的指示,最好使用 StreamBuilder 处理上下文。因此,我更改为 StreamBuilder,从而减少了所需的代码量
使用有状态的小部件,我有这个构建函数:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Icon(MdiIcons.atom),
Text("Message Channel")
],
)
),
body: compileWidget(context),
bottomSheet: Row(
children: [
Expanded(
child: DefaultTextFormField(true, null, hintText: "Message ...", controller: messageField)
),
IconButton(
icon: Icon(Icons.send),
onPressed: onSendPressed
)
],
),
);
}
正如您在正文中所见,有一个 compileWidget
函数定义为:
FutureBuilder<Widget> compileWidget(BuildContext context) {
return FutureBuilder(
future: createWidget(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
print("DONE loading widget ");
return snapshot.data;
} else {
return Container(
child: Center(
child: CircularProgressIndicator()
),
);
}
}
);
}
反过来,createWidget
函数如下所示:
List<Widget> bubbles;
Future<Widget> createWidget(BuildContext context) async {
await updateChatList();
// when working, return listview w/ children of bubbles
return Padding(
padding: EdgeInsets.all(10),
child: ListView(
children: this.bubbles,
),
);
}
// update chat list below
Future<void> updateChatList({Message message}) async {
if (this.bubbles != null) {
if (message != null) {
print("Pushing new notification into chat ...");
this.bubbles.add(bubbleFrom(message));
} else {
print("Update called, but nothing to do");
}
} else {
var initMessages = await Message.getMessagesBetween(this.widget.implicatedCnac.implicatedCid, this.widget.peerNac.peerCid);
print("Loaded ${initMessages.length} messages between ${this.widget.implicatedCnac.implicatedCid} and ${this.widget.peerNac.peerCid}");
// create init bubble list
this.bubbles = initMessages.map((e) {
print("Adding bubble $e");
return bubbleFrom(e);
}).toList();
print("Done loading bubbles ...");
}
}
聊天气泡井然有序地出现在屏幕上。但是,一旦收到新消息并被以下人员接收:
StreamSubscription<Message> listener;
@override
void initState() {
super.initState();
this.listener = Utils.broadcaster.stream.stream.listen((message) async {
print("{${this.widget.implicatedCnac.implicatedCid} <=> ${this.widget.peerNac.peerCid} streamer received possible message ...");
if (this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid) {
print("Message meant for this screen!");
await this.updateChatList(message: message);
setState(() {});
}
});
}
“针对此屏幕的消息!”打印,然后在 updateChatList
内打印“Pushing new notification into chat ...”,这意味着气泡已添加到数组中。最后,setState 被调用,但是额外的气泡没有被渲染。这里可能出了什么问题?
首先,让我解释一下 setState
是如何根据这个 link 工作的。
每当您更改 State
对象的内部状态时,请在传递给 setState
的函数 中进行更改。调用 setState
通知框架此对象的内部状态已更改,可能会影响此子树中的用户界面,这会导致框架为此 State
对象安排构建。
对于你的情况,我会说这只是一个惯例。您可以在有状态 class 中定义一个消息对象,并在侦听器和 setstate
中更新它,如下所示:
setState(() { this.message = message; });
注意:
任何更改之前,您需要检查这个问题
因为将 setstete
与 FutureBuilder
一起使用会在 StatefulWidget
中形成无限循环,从而降低性能和灵活性。也许,您应该改变设计屏幕的方法,因为 FutureBuilder 每次尝试获取数据时都会自动更新状态。
更新:
您的问题有更好的解决方案。因为您正在使用流来读取消息,所以您可以使用 StreamBuilder 而不是侦听器和 FutureBuilder
。它为提供数据流的服务带来更少的实现和更高的可靠性。每次 StreamBuilder
从 Stream 接收到一条消息,它会显示任何你想要的数据快照。
您需要确保 ListView 具有 UniqueKey
:
ScrollController _scrollController = ScrollController();
Stream<Widget> messageStream;
@override
void initState() {
super.initState();
// use RxDart to merge 2 streams into one stream
this.messageStream = Rx.merge<Message>([Utils.broadcaster.stream.stream.where((message) => this.widget.implicatedCnac.implicatedCid == message.implicatedCid && this.widget.peerNac.peerCid == message.peerCid), this.initStream()]).map((message) {
print("[MERGE] stream recv $message");
this.widget.bubbles.add(bubbleFrom(message));
// this line below to ensure that, as new items get added, the listview scrolls to the bottom
this._scrollController = _scrollController.hasClients ? ScrollController(initialScrollOffset: _scrollController.position.maxScrollExtent) : ScrollController();
return ListView(
key: UniqueKey(),
controller: this._scrollController,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
padding: EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 50),
children: this.widget.bubbles,
);
});
}
注意:根据 Erfan 的指示,最好使用 StreamBuilder 处理上下文。因此,我更改为 StreamBuilder,从而减少了所需的代码量