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); } }
修复后的逻辑说明
- 用户点击发送后,先插入自己的消息,再插入一条
isLoading: true的机器人消息,此时界面会显示打字指示器。 - 调用Dialogflow获取响应,拿到结果后,更新那条加载中的消息,把
isLoading设为false并填入实际内容,界面就会自动切换成消息气泡。 - 每条机器人消息都有自己的加载状态,完全独立,不会互相干扰,这样每次发送消息都会显示打字指示器直到响应返回。
内容的提问来源于stack exchange,提问作者rakan armoush
相关产品推荐
相关产品推荐

