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

Riverpod存储字符串列表的状态为何总是重置?如何修复?

问题描述

我构建了如下Riverpod状态:

import "package:riverpod_annotation/riverpod_annotation.dart";

part "form_state.g.dart";

@riverpod
class FormState extends _$FormState {

  @override
  List<String?> build() {
    ref.listenSelf((previous, next) {
      log("previous: $previous, next: $next", name: "FORMSTATE 0");
    });

    return List.filled(10, null);
  }

  void change(int index, String value) {
    log("change: $index, value: $value", name: "FORMSTATE 1");
    state = [
      ...state.sublist(0, index),
      value,
      ...state.sublist(index + 1),
    ];
  }
}

在Widget中使用了一组TextField:

class DummyPage extends ConsumerStatefulWidget {
  const DummyPage({super.key});

  @override
  ConsumerState<DummyPage> createState() => _DummyPageState();
}

class _DummyPageState extends ConsumerState<DummyPage> {
  final List<TextEditingController> _controllers =
      List.generate(10, (index) => TextEditingController());

  @override
  void initState() {
    final form = ref.read(formStateProvider);

    for (var i = 0; i < _controllers.length; i++) {
      _controllers[i].text = form[i] ?? "";
    }

    super.initState();
  }

  @override
  void dispose() {
    for (var i = 0; i < _controllers.length; i++) {
      _controllers[i].dispose();
    }

    super.dispose();
  }

  @override
  Widget build(context) {
    return Column(children: [
      ...List.generate(
        _controllers.length,
        (index) => TextField(
          controller: _controllers[index],
          onChanged: (val) {
            ref.read(formStateProvider.notifier).change(index, val);
          }
        )
    ])
  }
}

当前每次调用onChanged时,状态总会重置,日志输出如下:

[FORMSTATE 0] previous: null, next: [null, null, null, null, null, null]
[FORMSTATE 1] change: 0, value: 2
[FORMSTATE 0] previous: [null, null, null, null, null, null], next: [2, null, null, null, null, null]

[FORMSTATE 0] previous: null, next: [null, null, null, null, null, null]
[FORMSTATE 1] change: 1, value: 3
[FORMSTATE 0] previous: [null, null, null, null, null, null], next: [null, 3, null, null, null, null]

[FORMSTATE 0] previous: null, next: [null, null, null, null, null, null]
[FORMSTATE 1] change: 2, value: 9
[FORMSTATE 0] previous: [null, null, null, null, null, null], next: [null, null, 9, null, null, null]

请问导致该问题的原因是什么?如何修改才能实现当列表中索引i的TextField值变化时,Riverpod状态的对应索引i的值也同步更新?


问题原因

核心问题是Widget未监听Riverpod状态变化,且状态实例未被持续订阅:

  1. build方法中没有用ref.watch订阅formStateProvider,状态更新时不会触发Widget重建,无法同步最新状态到输入框控制器。
  2. initState仅在初始化时读取一次状态,后续状态更新后不会同步到TextEditingController;同时因为没有监听状态,Provider可能因作用域变化被销毁重建,导致每次操作拿到的都是初始状态实例。

修改方案

方案1:让Widget监听状态并同步控制器

修改DummyPage代码,通过ref.watch订阅状态,在状态更新时同步输入框控制器的文本:

class DummyPage extends ConsumerStatefulWidget {
  const DummyPage({super.key});

  @override
  ConsumerState<DummyPage> createState() => _DummyPageState();
}

class _DummyPageState extends ConsumerState<DummyPage> {
  final List<TextEditingController> _controllers =
      List.generate(10, (index) => TextEditingController());

  @override
  void initState() {
    super.initState();
    // 初始化时同步初始状态到控制器
    final initialState = ref.read(formStateProvider);
    for (var i = 0; i < _controllers.length; i++) {
      _controllers[i].text = initialState[i] ?? "";
    }
  }

  @override
  void dispose() {
    for (var controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 监听状态变化,同步最新状态到对应控制器
    final formState = ref.watch(formStateProvider);
    for (var i = 0; i < formState.length; i++) {
      // 避免不必要的文本赋值,防止光标跳动
      if (_controllers[i].text != formState[i]) {
        _controllers[i].text = formState[i] ?? "";
      }
    }

    return Column(children: [
      ...List.generate(
        _controllers.length,
        (index) => TextField(
          controller: _controllers[index],
          onChanged: (val) {
            ref.read(formStateProvider.notifier).change(index, val);
          },
        ),
      ),
    ]);
  }
}

方案2:优化状态更新逻辑(可选)

可以简化FormState的change方法写法,逻辑与原代码一致但更清晰:

void change(int index, String value) {
  log("change: $index, value: $value", name: "FORMSTATE 1");
  final newState = List<String?>.from(state);
  newState[index] = value;
  state = newState;
}

关键说明

  • ref.watch会订阅Provider,状态更新时自动触发Widget重建,确保Widget始终持有最新状态。
  • 添加文本判断可以避免重复赋值导致的输入光标异常跳动问题。

内容的提问来源于stack exchange,提问作者sempraEdic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:35:25