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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 06:42:47