You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:如何每次调用函数时重复执行Future?打字指示器仅首次显示问题

解决Flutter聊天机器人每次发送消息都显示打字指示器的问题

我看了你的代码,发现核心问题有两个:一是全局的_nextWidget状态在第一次触发后就一直是true,后续调用bot()函数时直接返回消息组件,不会再显示指示器;二是固定的5.5秒延迟和实际Dialogflow的响应时间不匹配,体验会很差。

咱们可以通过给每条消息添加独立的加载状态来解决这个问题,让每条机器人消息自己控制是否显示打字指示器,具体修改如下:

步骤1:调整消息结构,添加加载状态标识

把原来的消息列表改成每条消息包含isLoading字段,用来标记这条机器人消息是否处于加载中。

步骤2:修改发送消息的逻辑

用户发送消息后,先插入一条加载中的机器人消息(isLoading: true),然后再调用Dialogflow获取响应,拿到响应后更新这条消息的内容并关闭加载状态。

步骤3:重构bot组件的渲染逻辑

根据每条消息的isLoading状态来决定显示打字指示器还是实际消息内容,移除全局的_nextWidget变量。

修改后的完整代码

import 'package:bubble/bubble.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_bubble/bubble_type.dart';
import 'package:flutter_chat_bubble/chat_bubble.dart';
import 'package:flutter_chat_bubble/clippers/chat_bubble_clipper_2.dart';
import 'package:flutter_dialogflow/dialogflow_v2.dart';
import 'package:progress_indicators/progress_indicators.dart';

void main() { runApp(Bot()); }

class Bot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'bot',
      theme: ThemeData(
        primarySwatch: Colors.grey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      debugShowCheckedModeBanner: false,
      home: HomePage(title: 'bot'),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final messageInsert = TextEditingController();
  // 修改消息结构:添加isLoading字段标记加载状态
  List<Map> messsages = List();

  @override
  void initState() {
    super.initState();
  }

  void response(int loadingMessageIndex) async {
    AuthGoogle authGoogle = await AuthGoogle(
      fileJson: "assets/credentials.json").build();
    Dialogflow dialogflow = Dialogflow(authGoogle: authGoogle, language: Language.english);
    AIResponse aiResponse = await dialogflow.detectIntent(messsages[1]["message"]);
    
    setState(() {
      // 更新加载中的消息为实际响应内容
      messsages[loadingMessageIndex] = {
        "data": 0,
        "message": aiResponse.getListMessage()[0]["text"]["text"][0].toString(),
        "isLoading": false
      };
    });
    print(aiResponse.getListMessage()[0]["text"]["text"][0].toString());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[850],
      appBar: AppBar(
        leading: IconButton(icon: Icon(Icons.menu, color: Colors.white, size: 45)),
        backgroundColor: Colors.grey[800],
      ),
      body: Container(
        child: Column(
          children: [
            Flexible(
              child: ListView.builder(
                reverse: true,
                itemCount: messsages.length,
                itemBuilder: (context, index) => chat(
                    messsages[index]["message"].toString(), 
                    messsages[index]["data"],
                    messsages[index]["isLoading"] ?? false)
              ),
            ),
            Container(
              color: Colors.grey[850],
              child: ListTile(
                leading: IconButton(
                  icon: Icon(Icons.mic_off, color: Colors.white, size: 35),
                ),
                title: Container(
                  height: 45,
                  decoration: BoxDecoration(
                    shape: BoxShape.rectangle,
                    borderRadius: BorderRadius.all(Radius.circular(10)),
                    color: Colors.white,
                  ),
                  padding: EdgeInsets.only(left: 10),
                  child: TextFormField(
                    cursorColor: Colors.grey[850],
                    controller: messageInsert,
                    decoration: InputDecoration(
                      hintText: "Chat with me",
                      hintStyle: TextStyle(
                        color: Colors.black
                      ),
                    ),
                    style: TextStyle(
                      fontSize: 16,
                      color: Colors.black
                    ),
                    onChanged: (value) {}
                  )
                ),
                trailing: IconButton(
                  icon: Icon(
                    Icons.send, size: 30.0, color: Colors.greenAccent,
                  ),
                  onPressed: () {
                    if (messageInsert.text.isEmpty) {
                      print("empty message");
                    } else {
                      setState(() {
                        // 插入用户消息
                        messsages.insert(0, {"data": 1, "message": messageInsert.text});
                        // 插入加载中的机器人消息,记录它的索引
                        int loadingIndex = 0;
                        messsages.insert(loadingIndex, {
                          "data": 0,
                          "message": "",
                          "isLoading": true
                        });
                      });
                      // 传入加载消息的索引,用于后续更新
                      response(0);
                      messageInsert.clear();
                    }
                    FocusScopeNode currentFocus = FocusScope.of(context);
                    if (!currentFocus.hasPrimaryFocus) {
                      currentFocus.unfocus();
                    }
                  })
              )
            ),
            SizedBox(height: 10.0)
          ]
        ),
      )
    );
  }

  Widget bot(String message, bool isLoading) {
    // 根据当前消息的isLoading状态决定显示指示器还是消息
    return isLoading ? botInd() : botMessage(message);
  }

  Widget botInd() {
    return Container(
      alignment: Alignment.bottomLeft,
      margin: EdgeInsets.only(top: 20),
      child: Container(
        constraints: BoxConstraints(maxWidth: 75, maxHeight: 100),
        child: JumpingDotsProgressIndicator(fontSize: 50.0, color: Colors.white)
      )
    );
  }

  Widget botMessage(String message) {
    return ChatBubble(
      clipper: ChatBubbleClipper2(type: BubbleType.receiverBubble),
      alignment: Alignment.bottomLeft,
      margin: EdgeInsets.only(top: 20),
      backGroundColor: Colors.white,
      child: Container(
        constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
        child: Text(
          message,
          style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)
        )
      )
    );
  }

  Widget user(String message) {
    return ChatBubble(
      clipper: ChatBubbleClipper2(type: BubbleType.sendBubble),
      alignment: Alignment.bottomRight,
      margin: EdgeInsets.only(top: 20),
      backGroundColor: Colors.white,
      child: Container(
        constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
        child: Text(
          message,
          style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)
        )
      )
    );
  }

  Widget chat(String message, int data, bool isLoading) {
    return data == 0 ? bot(message, isLoading) : user(message);
  }
}

修复后的逻辑说明

  1. 用户点击发送后,先插入自己的消息,再插入一条isLoading: true的机器人消息,此时界面会显示打字指示器。
  2. 调用Dialogflow获取响应,拿到结果后,更新那条加载中的消息,把isLoading设为false并填入实际内容,界面就会自动切换成消息气泡。
  3. 每条机器人消息都有自己的加载状态,完全独立,不会互相干扰,这样每次发送消息都会显示打字指示器直到响应返回。

内容的提问来源于stack exchange,提问作者rakan armoush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:52:54