如何在Riverpod的ConsumerWidget中正确使用TextEditingController?
在Riverpod的ConsumerWidget中正确使用TextEditingController的解决方案
问题根源
你遇到的文本框内容被清空的问题,核心原因是每次build方法执行时都会重新创建TextEditingController实例。ConsumerWidget的build会在监听的Provider状态变化(比如roomDataProvider更新)、UI布局调整等场景下触发,新创建的控制器会覆盖旧实例,导致之前输入的内容丢失——点击键盘完成按钮时刚好触发了组件重建,所以文本框内容被清空。
方案1:改用ConsumerStatefulWidget(推荐)
Riverpod提供了ConsumerStatefulWidget和ConsumerState,让你既能像普通StatefulWidget一样管理组件本地状态(比如TextEditingController),又能保留使用ref监听Provider的能力。
修改后的代码示例:
class MainPage extends ConsumerStatefulWidget { const MainPage({super.key}); @override ConsumerState<MainPage> createState() => _MainPageState(); } class _MainPageState extends ConsumerState<MainPage> { late final TextEditingController roomIDController; late final TextEditingController roomPasswordController; @override void initState() { super.initState(); // 仅在组件初始化时创建控制器,避免重复实例化 roomIDController = TextEditingController(); roomPasswordController = TextEditingController(); } @override void dispose() { // 组件销毁时释放控制器,避免内存泄漏 roomIDController.dispose(); roomPasswordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // 正常监听Provider状态 final roomListData = ref.watch(roomDataProvider); final roomService = RoomService(); final size = MediaQuery.of(context).size; double deviceWidth = size.width; double deviceHeight = size.height; // 后续UI代码保持不变,直接使用已初始化的控制器 return Scaffold( backgroundColor: bluePrimary, body: SingleChildScrollView( child: roomListData.when( data: (data) { List<RoomModelResponse> roomList = data.map((room) => room).toList(); return SafeArea( child: Container( padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 16), child: Column( children: [ // ... 原有的UI代码,直接使用roomIDController和roomPasswordController ], ), ), ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), ), ), ); } }
方案2:用Provider管理输入状态(适合跨组件共享场景)
如果你的输入内容需要在其他组件中使用,或者希望把状态完全交给Riverpod管理,可以用StateProvider存储输入文本值,再绑定到TextField的控制器上。
第一步:定义状态Provider
final roomIDProvider = StateProvider<String>((ref) => ''); final roomPasswordProvider = StateProvider<String>((ref) => '');
第二步:在MainPage中使用
class MainPage extends ConsumerWidget { const MainPage({super.key}); @override Widget build(BuildContext context, ref) { final roomListData = ref.watch(roomDataProvider); final roomService = RoomService(); final size = MediaQuery.of(context).size; double deviceWidth = size.width; double deviceHeight = size.height; // 监听输入状态 final roomID = ref.watch(roomIDProvider); final roomPassword = ref.watch(roomPasswordProvider); return Scaffold( backgroundColor: bluePrimary, body: SingleChildScrollView( child: roomListData.when( data: (data) { List<RoomModelResponse> roomList = data.map((room) => room).toList(); return SafeArea( child: Container( padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 16), child: Column( children: [ // ... 其他UI代码 // 房间ID输入框 SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: TextField( controller: TextEditingController.fromValue( TextEditingValue( text: roomID, selection: TextSelection.collapsed(offset: roomID.length), ), ), onChanged: (value) { // 更新Provider状态 ref.read(roomIDProvider.notifier).state = value; }, decoration: InputDecoration( filled: true, fillColor: whitePrimary, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), hintText: 'Enter room ID to join it', hintStyle: const TextStyle(color: Color.fromARGB(255, 174, 173, 173)), ), ), ), // 密码输入框 SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: TextField( obscureText: true, controller: TextEditingController.fromValue( TextEditingValue( text: roomPassword, selection: TextSelection.collapsed(offset: roomPassword.length), ), ), onChanged: (value) { ref.read(roomPasswordProvider.notifier).state = value; }, decoration: InputDecoration( filled: true, fillColor: whitePrimary, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), hintText: 'Leave blank if the room has no password', hintStyle: const TextStyle(color: Color.fromARGB(255, 174, 173, 173)), ), ), ), ], ), ), ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), ), ), ); } }
方案选择建议
- 方案1适合仅在当前组件使用输入内容的场景,代码简洁且符合常规StatefulWidget使用习惯,是优先推荐的方案。
- 方案2适合需要跨组件共享输入状态的场景,把状态完全交给Riverpod管理,便于统一维护。
内容的提问来源于stack exchange,提问作者Geo48
相关产品推荐
相关产品推荐

