【问题标题】:setState not causing UI to refreshsetState 不会导致 UI 刷新
【发布时间】:2023-04-01 08:35:01
【问题描述】:

使用有状态小部件,我有这个构建功能:

  @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 内,“将新通知推送到聊天中......”打印,暗示气泡被添加到数组中。最后,调用了 setState,但是,额外的气泡没有被渲染。这里可能出了什么问题?

【问题讨论】:

    标签: flutter setstate


    【解决方案1】:

    首先,让我解释一下setState是如何根据这个link工作的。

    每当您更改State 对象的内部状态时,请在您传递给setState 的函数中进行更改。调用 setState 会通知框架此对象的内部状态已发生更改,可能会影响此子树中的用户界面,这会导致框架为此 State 对象安排构建。

    在你的情况下,我会说这只是一个约定。您可以在有状态的类中定义一个消息对象,并在侦听器和您的setstate 中更新它,如下所示:

    setState(() { this.message = message; });
    

    注意:

    在进行任何更改之前,您需要检查此问题

    Usage of FutureBuilder with setState

    因为将setsteteFutureBuilder 一起使用会在StatefulWidget 中产生无限循环,从而降低性能和灵活性。或许,您应该改变设计屏幕的方法,因为 FutureBuilder 每次尝试获取数据时都会自动更新状态。

    更新:

    您的问题有更好的解决方案。因为您使用的是流来读取消息,所以您可以使用StreamBuilder 代替侦听器和FutureBuilder。它为提供数据流的服务带来更少的实现和更高的可靠性。每次StreamBuilder 收到来自 Stream 的消息时,它都会显示任何你想要的数据快照。

    【讨论】:

    • 感谢您的回复。在代码中,没有无限循环,因为我没有在compileWidget 使用的函数内的任何地方调用 setState。我尝试在广播消息接收器的末尾添加setState(() { this.message = message; });,但除非导航器完全推送新屏幕,否则它仍然不起作用(这当然是浪费能源)
    • 以防万一,我将尝试第二个链接中的方法,看看它是否有效
    • @ThomasBraun 还有另一个对您有帮助的选择。使用StreamBuilder 代替侦听器,FutureBuilder 使用此链接:api.flutter.dev/flutter/widgets/StreamBuilder-class.html
    • 但是在这种情况下使用 StreamBuilder 比 FutureBuilder 更好,所以感谢您的建议。允许我使用更少的代码行:)
    • 好的!事实证明,我需要在 ListView 中添加一个 UniqueKey()!现在,它按预期工作了
    【解决方案2】:

    你需要确保 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,从而减少了所需的代码量

    【讨论】:

    • 如果有人能解释为什么这会奏效,那对社区来说会很棒
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-07-03
    相关资源
    最近更新 更多