Flutter Riverpod页面返回后调用ref.read报已释放异常求助
解决CallScreen页面返回后调用ref.read抛出已释放异常的问题
问题原因
当你pop掉CallScreen页面时,对应的_CallScreenState会被销毁,但如果此时sendMessage内的异步请求(ChatGptApi.getChatGptAnswer)仍在运行,请求完成后旧的state实例会尝试调用已被销毁的ref,触发"Bad state: Cannot use 'ref' after the widget was disposed"异常。你仅在sendMessage开头检查了一次mounted,异步操作完成后未重复验证;且再次返回页面时会创建新的state实例,旧异步任务持有的是旧state的ref,自然无法正常使用。
解决方案
方法一:异步操作全程检查mounted状态
在每次调用ref.read、setState前都验证当前state是否仍处于活跃状态:
void sendMessage(String text) async { if (!mounted) return; // 添加用户消息 addMessageToList(text: text, received: false, isLoading: false); setState(() { isLoadingSendMessage = true; }); final oldMessages = ref.read(messagesProvider); final assistantMessage = addMessageToList( text: '', received: true, isLoading: isLoadingSendMessage); // 异步调用GPT final answer = await ChatGptApi.getChatGptAnswer(text, oldMessages); // 异步完成后先检查mounted if (!mounted) return; setState(() { isLoadingSendMessage = false; }); if (answer.trim().isNotEmpty) { bool error = answer.trim() == 'An internal error occurred'; // 更新消息前再次检查 if (!mounted) return; updateMessageInList( messageModel: assistantMessage, text: answer.trim(), isLoading: isLoadingSendMessage, error: error); systemSpeak(answer); } }
同时在操作ref的工具方法中也添加检查:
MessageModel addMessageToList( {required String text, required bool received, required bool isLoading}) { if (!mounted) return MessageModel(text: text, received: received, isLoading: isLoading); final messageModel = MessageModel(text: text, received: received, isLoading: isLoading); ref.read(messagesProvider.notifier).addMessage(messageModel); return messageModel; } void updateMessageInList( {required MessageModel messageModel, required String text, required bool isLoading, required bool error}) { if (!mounted) return; ref .read(messagesProvider.notifier) .updateMessage(messageModel, text, isLoading, error); }
方法二:用CancelableOperation取消异步任务
在页面销毁时主动取消未完成的异步请求,避免旧任务继续执行:
class _CallScreenState extends ConsumerState<CallScreen> { CancelableOperation? _gptRequestOperation; @override void dispose() { _gptRequestOperation?.cancel(); super.dispose(); } void sendMessage(String text) async { if (!mounted) return; addMessageToList(text: text, received: false, isLoading: false); setState(() { isLoadingSendMessage = true; }); final oldMessages = ref.read(messagesProvider); final assistantMessage = addMessageToList( text: '', received: true, isLoading: isLoadingSendMessage); // 将异步请求包装为可取消操作 _gptRequestOperation = CancelableOperation.fromFuture( ChatGptApi.getChatGptAnswer(text, oldMessages), ); try { final answer = await _gptRequestOperation?.value; if (!mounted || answer == null) return; setState(() { isLoadingSendMessage = false; }); if (answer.trim().isNotEmpty) { bool error = answer.trim() == 'An internal error occurred'; if (!mounted) return; updateMessageInList( messageModel: assistantMessage, text: answer.trim(), isLoading: isLoadingSendMessage, error: error); systemSpeak(answer); } } on OperationCanceledException { // 请求被取消,无需额外处理 } finally { _gptRequestOperation = null; } } }
方法三:将异步逻辑移至Provider处理
把GPT请求和消息更新逻辑迁移到StateNotifier中,组件仅负责触发请求和监听结果,彻底避开组件生命周期与异步任务的冲突:
// messages_provider.dart final messagesProvider = StateNotifierProvider<MessagesNotifier, List<MessageModel>>((ref) { return MessagesNotifier(); }); class MessagesNotifier extends StateNotifier<List<MessageModel>> { MessagesNotifier() : super([]); Future<void> sendUserMessage(String text) async { // 添加用户消息 final userMessage = MessageModel(text: text, received: false, isLoading: false); state = [...state, userMessage]; // 添加加载中消息 final loadingMessage = MessageModel(text: '', received: true, isLoading: true); state = [...state, loadingMessage]; try { final answer = await ChatGptApi.getChatGptAnswer(text, state); bool error = answer.trim() == 'An internal error occurred'; // 更新加载中消息为结果 final updatedMessage = loadingMessage.copyWith( text: answer.trim(), isLoading: false, error: error, ); state = state.map((m) => m == loadingMessage ? updatedMessage : m).toList(); // 语音播报逻辑可在此处理,或通过回调通知组件 } catch (e) { final errorMessage = loadingMessage.copyWith( text: '请求失败', isLoading: false, error: true, ); state = state.map((m) => m == loadingMessage ? errorMessage : m).toList(); } } }
然后在CallScreen中简化调用:
void sendMessage(String text) { if (!mounted) return; ref.read(messagesProvider.notifier).sendUserMessage(text); }
内容的提问来源于stack exchange,提问作者mouhamed menasria
相关产品推荐
相关产品推荐

