Flutter中Riverpod的StateProvider更新值错误 按钮状态相反如何修复
问题描述
在POC应用中使用riverpod实现状态管理,当前页面搭载文本编辑控制器,预期逻辑为:判断TextField输入文本是否为空,据此动态控制按钮的启用/禁用状态。实际运行时按钮状态始终与预期逻辑相反,需要修复。
原问题代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:to_do_list/models/task.dart'; import 'package:to_do_list/reusable_widgets/app_bar.dart'; class TaskScreen extends ConsumerStatefulWidget { const TaskScreen({Key? key}) : super(key: key); @override TaskScreenState createState() => TaskScreenState(); } class TaskScreenState extends ConsumerState<TaskScreen> { DateTime? _selectedDate; static final TextEditingController _titleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); var textProvider = StateProvider((_) => _titleController.text.isNotEmpty); @override Widget build(BuildContext context) { bool value = ref.watch(textProvider); return Scaffold( resizeToAvoidBottomInset: false, appBar: const AppBarWrapper( title: "To-Do List", ), body: Stack(children: [ Padding( padding: const EdgeInsets.all(14.0), child: Column( children: [ TextField( controller: _titleController, onChanged: (val) { ref.read(textProvider.state).state = val.isEmpty; }, autofocus: true, decoration: const InputDecoration(hintText: "Title"), ), const SizedBox( height: 14, ), TextField( controller: _descriptionController, decoration: const InputDecoration(hintText: "Description"), minLines: 3, maxLines: 5, ), SizedBox( width: 150, child: ListTile( leading: const Icon(Icons.calendar_today), title: Text( _selectedDate?.toString() ?? 'No Date', ), onTap: () async { _selectedDate = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); }, ), ), ], ), ), Positioned( bottom: MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, child: ElevatedButton( onPressed: value ? () { Navigator.of(context).pop(Task( title: _titleController.text, description: _descriptionController.text.isNotEmpty ? _descriptionController.text : null, date: _selectedDate)); } : null, child: const Icon( Icons.done, ), ), ), ]), ); } }
问题原因
核心错误是布尔判断逻辑前后不一致:
textProvider初始化时,初始值设置为_titleController.text.isNotEmpty,即文本非空时状态为true,对应按钮可点击- 输入框
onChanged回调中,给provider赋值写的是val.isEmpty,即文本为空时状态为true,对应按钮可点击
两处逻辑完全反向,就会出现空文本时按钮可点、输入内容后按钮禁用的异常表现。
另外代码还有两个隐性问题:
_titleController被加了static修饰,页面销毁重建时控制器不会重置,会残留上次输入的内容- 日期选择逻辑拿到
_selectedDate后没有调用setState,选完日期页面不会刷新显示新选中的日期
修复方法
- 修正
onChanged中的赋值逻辑,和初始判断规则保持一致,将val.isEmpty改为val.isNotEmpty - 去掉
_titleController的static修饰,避免页面重建时的文本残留 - 在日期选择的
await逻辑后添加setState,触发页面刷新显示选中的日期
修复后对应修改的关键代码段:
class TaskScreenState extends ConsumerState<TaskScreen> { DateTime? _selectedDate; // 去掉static修饰 final TextEditingController _titleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); var textProvider = StateProvider((_) => _titleController.text.isNotEmpty); @override Widget build(BuildContext context) { bool value = ref.watch(textProvider); return Scaffold( // ...省略其他未改动代码 body: Stack(children: [ Padding( padding: const EdgeInsets.all(14.0), child: Column( children: [ TextField( controller: _titleController, onChanged: (val) { // 修正判断逻辑,和初始值规则统一 ref.read(textProvider.state).state = val.isNotEmpty; }, autofocus: true, decoration: const InputDecoration(hintText: "Title"), ), // ...省略其他未改动组件 SizedBox( width: 150, child: ListTile( leading: const Icon(Icons.calendar_today), title: Text( _selectedDate?.toString() ?? 'No Date', ), onTap: () async { _selectedDate = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); // 补上setState刷新日期显示,加mounted判断避免组件销毁后报错 if (mounted) setState(() {}); }, ), ), ], ), ), // ...省略未改动的按钮代码 ]), ); } }
额外提示:如果页面里的provider只在当前State类使用,不建议把provider定义在State内部,最好放到全局作用域,避免页面重建时重复创建provider实例。
内容的提问来源于stack exchange,提问作者Roland
相关产品推荐
相关产品推荐

