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

Flutter Riverpod FutureProvider点击按钮不显示加载指示器问题

问题

使用Riverpod实现搜索功能时,创建了带autoDispose的family类型FutureProvider,初始化Widget时通过watch监听带默认参数的Provider;点击按钮传入新参数执行搜索时逻辑正常,但CircularProgressIndicator加载指示器不显示,请问原因是什么?

服务代码

import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:i_condominio/models/Condominio.dart';

final condominioPRovider = Provider((ref) => CondominioService());

final condFuture =
    FutureProvider.autoDispose.family<List<Condominio>, String>((ref, arg) {
  final serviceProvider = ref.watch(condominioPRovider);
  return serviceProvider.findByCepFuture(arg);
});

class CondominioService {
  Future<List<Condominio>> findByCepFuture(String cepProcura) async {
    List<Condominio> listCondominio = [
      Condominio(
          nome: 'Porto panorama',
          cep: Cep(
              cep: "11075-350",
              logradouro: "Rua Monsenhor Paula Rodrigues",
              complemento: "",
              bairro: "Vila Belmiro",
              localidade: "Santos",
              uf: "SP",
              ibge: "3548500"),
          numero: "129"),
      Condominio(
          nome: 'Central Park',
          cep: Cep(
              cep: "11075-350",
              logradouro: "Rua Monsenhor Paula Rodrigues",
              complemento: "",
              bairro: "Vila Belmiro",
              localidade: "Santos",
              uf: "SP",
              ibge: "3548500"),
          numero: "126"),
      Condominio(
          nome: 'Sao Vicente park',
          cep: Cep(
              cep: "11380-120",
              logradouro: "Rua Monsenhor Paula Rodrigues",
              complemento: "",
              bairro: "Vila Belmiro",
              localidade: "Santos",
              uf: "SP",
              ibge: "3548500"),
          numero: "130")
    ];
    print('buscando $cepProcura');
    await Future.delayed(const Duration(seconds: 1));
    final retorno = listCondominio
        .where((element) => element.cep.cep == cepProcura)
        .toList();
    print('acabou ${retorno.length}');
    return Future.value(retorno);
  }
}

Widget代码

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:i_condominio/models/Condominio.dart';
import 'package:i_condominio/services/CondominioService.dart';
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';

class SelecionarCondominioPage extends ConsumerWidget {
  const SelecionarCondominioPage({super.key});
  selecionarCondoninio(Condominio e) {
    print('condominio Selecionado ${e.nome}');
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cf = ref.watch(condFuture('11380-120'));

    var maskFormatter = MaskTextInputFormatter(
        mask: '#####-###',
        filter: {"#": RegExp(r'[0-9]')},
        type: MaskAutoCompletionType.lazy);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Novo usuario'),
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              inputFormatters: [maskFormatter],
              decoration: const InputDecoration(
                labelText: 'CEP',
              ),
              keyboardType: TextInputType.number,
            ),
          ),
          const Padding(
            padding: EdgeInsets.all(8.0),
            child: Text(
              "Condominios encontrados",
              style: TextStyle(fontSize: 22),
            ),
          ),
          ElevatedButton(
            onPressed: () => ref.watch(condFuture('11075-350')),
            child: const Text('Buscar 11075-350'),
          ),
          cf.when(
              data: (data) => Expanded(
                    child: ListView(
                      children: [
                        ...data.map((e) => ListTile(
                            title: Text(e.nome),
                            subtitle: Text(e.enderecoCompleto),
                            onTap: () => showDialog(
                                  context: context,
                                  builder: ((ctx) => AlertDialog(
                                        title: const Text(
                                            'Condominio selecionado'),
                                        content: Text(
                                            'Confirma o condominio ${e.nome}'),
                                        actions: [
                                          const TextButton(
                                              onPressed: null,
                                              child: Text("OK")),
                                          TextButton(
                                              onPressed: () =>
                                                  Navigator.of(context).pop(),
                                              child: const Text("CANCELAR")),
                                        ],
                                      )),
                                )))
                      ],
                    ),
                  ),
              error: (error, n) => Text(error.toString()),
              loading: () => const Center(child: CircularProgressIndicator())),
        ],
      ),
    );
  }
}

原因分析

核心问题在于按钮点击时未更新Widget监听的Provider参数:

  1. 你在build方法中固定监听condFuture('11380-120'),这个Provider实例的状态已经处于data阶段
  2. 按钮点击时调用ref.watch(condFuture('11075-350'))只是获取了新的Provider实例,但Widget并未切换到监听这个新实例,因此不会触发loading状态的重建

另外,ref.watch仅用于在build方法中监听状态变化,不能直接在点击事件中调用,点击事件中应该用ref.read或状态管理来切换参数。


解决方案

需要添加一个状态Provider来保存当前搜索的CEP,让Widget根据动态参数监听对应的FutureProvider实例:

1. 添加状态Provider管理当前CEP

// 在服务代码中添加
final currentCepProvider = StateProvider<String>((ref) => '11380-120'); // 默认搜索CEP

2. 修改Widget监听逻辑

在build方法中根据当前CEP动态监听condFuture:

@override
Widget build(BuildContext context, WidgetRef ref) {
  final currentCep = ref.watch(currentCepProvider);
  final cf = ref.watch(condFuture(currentCep));

  var maskFormatter = MaskTextInputFormatter(
      mask: '#####-###',
      filter: {"#": RegExp(r'[0-9]')},
      type: MaskAutoCompletionType.lazy);
  // ... 其余代码不变
}

3. 修改按钮点击事件

通过更新状态Provider来切换搜索参数:

ElevatedButton(
  onPressed: () => ref.read(currentCepProvider.notifier).state = '11075-350',
  child: const Text('Buscar 11075-350'),
),

可选优化:支持TextField输入触发搜索

让用户输入的CEP自动更新搜索参数:

TextField(
  inputFormatters: [maskFormatter],
  decoration: const InputDecoration(
    labelText: 'CEP',
  ),
  keyboardType: TextInputType.number,
  onChanged: (value) {
    // 输入完整8位数字时自动触发搜索
    if (maskFormatter.getUnmaskedText().length == 8) {
      ref.read(currentCepProvider.notifier).state = value;
    }
  },
),

修改后,当切换CEP参数时,Widget会重建并监听新的condFuture实例,此时新Provider会进入loading状态,CircularProgressIndicator就能正常显示了。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 09:45:29