使用Riverpod处理异步数据:点击按钮自动填充TextField的最佳方案
解决方案:连接FutureProvider到UI并处理异步状态
一、当前场景下优先选择 FutureProvider + when()
你的需求是点击按钮触发API请求并填充输入框,同时支持手动编辑,这种简单的异步请求场景用 FutureProvider.family + AsyncValue.when() 最直接,代码量少且逻辑清晰。
修改后的完整UI实现代码
@override Widget build(BuildContext context, WidgetRef ref) { final cep = ref.watch(cepStateProvider); final cepResult = ref.watch(getCepProvider(cep)); // 监听请求结果,成功时填充地址输入框 ref.listen<AsyncValue<ResultCep>>(getCepProvider(cep), (previous, next) { next.whenData((data) { // 仅当请求成功时更新地址,避免覆盖用户手动输入 _addressController.text = data.logradouro; // 可扩展填充其他字段,例如bairro、cidade等 }); }); return Scaffold( body: Padding( padding: const EdgeInsets.all(12.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ buildCustomTextField( controller: _cepController, labelText: 'CEP', maxWidth: 250, validator: (value) {}, ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () { // 更新Cep状态,触发FutureProvider请求 ref.read(cepStateProvider.notifier).state = _cepController.text; }, style: ElevatedButton.styleFrom( minimumSize: const Size(100, 50), backgroundColor: colorPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: const Text("Buscar"), ), ), ], ), // 处理异步状态:加载、错误、成功 cepResult.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('Erro: ${error.toString()}'), data: (_) => const SizedBox.shrink(), // 数据已通过listen填充,这里无需额外显示 ), buildCustomTextField( controller: _addressController, labelText: 'Endereço', maxWidth: 400, validator: (value) {}, ), ], ), ), ); }
关键说明:
AsyncValue.when():专门用于处理Provider异步状态的方法,完美覆盖加载中、出错、成功三种场景。ref.listen():监听Provider状态变化,仅在请求成功时更新地址输入框,不会干扰用户手动编辑操作。- 补充了
Column布局包裹Row和地址输入框,修复原代码的布局结构问题。
二、什么时候适合用 NotifierProvider?
如果你的需求后续扩展为:
- 需要保存历史请求记录
- 允许用户取消正在进行的请求
- 对获取到的地址数据进行本地修改并同步状态
- 多组件共享复杂的异步状态逻辑
此时推荐使用 AsyncNotifierProvider(适用于异步场景的Notifier),它提供更灵活的状态控制能力。
重构为AsyncNotifierProvider的示例代码
1. 定义Notifier类
class CepNotifier extends AsyncNotifier<ResultCep?> { @override ResultCep? build() => null; // 初始状态为null Future<void> fetchCep(String cep) async { state = const AsyncLoading(); // 设置加载状态 try { final service = CepService(cep); final result = await service.fetchCep(); state = AsyncData(result); // 请求成功,保存数据 } catch (e) { state = AsyncError(e, StackTrace.current); // 请求失败,保存错误信息 } } } // 注册Provider final cepNotifierProvider = AsyncNotifierProvider<CepNotifier, ResultCep?>(CepNotifier.new);
2. 对应的UI代码
@override Widget build(BuildContext context, WidgetRef ref) { final cepAsyncValue = ref.watch(cepNotifierProvider); return Scaffold( body: Padding( padding: const EdgeInsets.all(12.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ buildCustomTextField( controller: _cepController, labelText: 'CEP', maxWidth: 250, validator: (value) {}, ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () { // 直接调用Notifier的fetch方法触发请求 ref.read(cepNotifierProvider.notifier).fetchCep(_cepController.text); }, style: ElevatedButton.styleFrom( minimumSize: const Size(100, 50), backgroundColor: colorPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: const Text("Buscar"), ), ), ], ), // 处理异步状态 cepAsyncValue.when( loading: () => const CircularProgressIndicator(), error: (error, stack) => Text('Erro: ${error.toString()}'), data: (result) { // 数据成功返回时填充地址输入框 if (result != null) { _addressController.text = result.logradouro; } return const SizedBox.shrink(); }, ), buildCustomTextField( controller: _addressController, labelText: 'Endereço', maxWidth: 400, validator: (value) {}, ), ], ), ), ); }
关键说明:
AsyncNotifier将异步请求逻辑封装在内部,UI层只需调用方法即可,逻辑更内聚。- 状态由Notifier主动管理,支持更复杂的状态转换逻辑。
内容的提问来源于stack exchange,提问作者Leonardo Maito
相关产品推荐
相关产品推荐

