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

Flutter FutureBuilder报错:initState中使用Riverpod引发依赖问题

解决Flutter FutureBuilder snapshot.data为null及Riverpod初始化错误问题

核心问题分析

  • 初始化错误根源:在initState()中调用fetchAccounts()时使用了ref.watch(),此时ConsumerState的依赖(UncontrolledProviderScope)还未完成初始化,导致请求可能获取到无效的Provider值,或Future执行异常,最终FutureBuilder无法拿到有效数据。
  • 额外问题:ListView嵌套SingleChildScrollView引发滚动冲突;搜索过滤逻辑效率低下,每个列表项单独判断过滤条件。

解决方案

方案1:调整Future初始化逻辑(兼容现有代码结构)

修改_AccountFinderState,避免在initState()中使用ref.watch(),改用ref.read()并将Future初始化移至didChangeDependencies(),同时修复滚动和过滤问题:

class _AccountFinderState extends ConsumerState<AccountFinder> {
  late Future<List<Account>> accounts;
  String searchString = "";
  bool _isInitialized = false; // 标记是否已完成初始化,避免重复执行请求

  // 将Provider值作为参数传入,不在方法内调用ref.watch
  Future<List<Account>> fetchAccounts(String userId, String companyId) async {
    final url = Uri.parse(
      'http://api_adress/GetAllActs?CID=$companyId',
    );
    final headers = {'Content-Type': 'application/json'};
    final response = await http.post(
      url,
      headers: headers,
      body: json.encode({
        "id": userId,
        "userName": "string",
        "password": "string",
        "loggedIn": true,
        "userActive": true,
        "userDeleteable": true
      }),
    );

    if (response.statusCode == 200) {
      List<dynamic> listAccountsJson = jsonDecode(response.body);
      return listAccountsJson
          .map((account) => Account.fromJson(account))
          .toList();
    } else {
      throw Exception('Failed to load items');
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 仅初始化一次,避免依赖变化时重复请求
    if (!_isInitialized) {
      final userID = ref.read(userStateProvider).id;
      final companyID = ref.read(companyStateProvider).comID;
      accounts = fetchAccounts(userID, companyID);
      _isInitialized = true;
    }
  }

  // 刷新数据时获取最新的Provider值
  void _refreshAccounts() {
    final userID = ref.read(userStateProvider).id;
    final companyID = ref.read(companyStateProvider).comID;
    setState(() {
      accounts = fetchAccounts(userID, companyID);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 搜索框部分保持不变
        SafeArea(
          child: Container(
            height: 40,
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                color: Theme.of(context)
                    .colorScheme
                    .onBackground
                    .withOpacity(0.1)),
            margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
            child: TextField(
              onTapOutside: (event) =>
                  FocusManager.instance.primaryFocus?.unfocus(),
              onChanged: (value) {
                setState(() {
                  searchString = value.toLowerCase();
                });
              },
              style: Theme.of(context).textTheme.bodyMedium!,
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: AppLocalizations.of(context)!.search,
                  hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
                      fontSize: 16,
                      fontWeight: FontWeight.w300,
                      color: Theme.of(context)
                          .colorScheme
                          .onBackground
                          .withOpacity(0.7)),
                  prefixIcon: const Icon(Icons.search)),
            ),
          ),
        ),
        // 修改为自定义刷新方法
        IconButton(onPressed: _refreshAccounts, icon: const Icon(Icons.refresh)),
        const SizedBox(height: 10),
        Expanded(
          child: FutureBuilder(
            future: accounts,
            builder: (context, AsyncSnapshot<List<Account>> snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                return Center(child: Text('Error: ${snapshot.error}'));
              } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
                return const Center(child: Text('No data available'));
              } else {
                // 先过滤整个列表再渲染,提升性能
                final filteredAccounts = snapshot.data!
                    .where((account) => account.actName!.toLowerCase().contains(searchString))
                    .toList();

                return ListView.separated(
                  shrinkWrap: true, // 解决滚动冲突,让ListView自适应高度
                  padding: const EdgeInsets.all(8),
                  itemCount: filteredAccounts.length,
                  itemBuilder: (BuildContext context, int index) {
                    final account = filteredAccounts[index];
                    return Card(
                      elevation: 3,
                      child: ListTile(
                        title: Text(account.actName!),
                        subtitle: Text('${account.actMobileNo}\n${account.actPhoneNo}\n'),
                      ),
                    );
                  },
                  separatorBuilder: (BuildContext context, int index) {
                    return const Divider();
                  },
                );
              }
            },
          ),
        ),
      ],
    );
  }
}

方案2:改用FutureProvider(Riverpod最佳实践)

将异步请求封装为FutureProvider,自动管理请求状态,无需手动维护Future对象:

1. 定义FutureProvider

final accountsProvider = FutureProvider<List<Account>>((ref) async {
  final user = ref.watch(userStateProvider);
  final company = ref.watch(companyStateProvider);
  final url = Uri.parse('http://api_adress/GetAllActs?CID=${company.comID}');
  final headers = {'Content-Type': 'application/json'};
  final response = await http.post(
    url,
    headers: headers,
    body: json.encode({
      "id": user.id,
      "userName": "string",
      "password": "string",
      "loggedIn": true,
      "userActive": true,
      "userDeleteable": true
    }),
  );

  if (response.statusCode == 200) {
    List<dynamic> listAccountsJson = jsonDecode(response.body);
    return listAccountsJson
        .map((account) => Account.fromJson(account))
        .toList();
  } else {
    throw Exception('Failed to load items');
  }
});

2. 修改AccountFinder页面

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

  @override
  ConsumerState<AccountFinder> createState() => _AccountFinderState();
}

class _AccountFinderState extends ConsumerState<AccountFinder> {
  String searchString = "";

  @override
  Widget build(BuildContext context) {
    // 监听FutureProvider的状态变化
    final accountsAsyncValue = ref.watch(accountsProvider);
    
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 搜索框部分保持不变
        SafeArea(
          child: Container(
            height: 40,
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                color: Theme.of(context)
                    .colorScheme
                    .onBackground
                    .withOpacity(0.1)),
            margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
            child: TextField(
              onTapOutside: (event) =>
                  FocusManager.instance.primaryFocus?.unfocus(),
              onChanged: (value) {
                setState(() {
                  searchString = value.toLowerCase();
                });
              },
              style: Theme.of(context).textTheme.bodyMedium!,
              decoration: InputDecoration(
                  border: InputBorder.none,
                  hintText: AppLocalizations.of(context)!.search,
                  hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
                      fontSize: 16,
                      fontWeight: FontWeight.w300,
                      color: Theme.of(context)
                          .colorScheme
                          .onBackground
                          .withOpacity(0.7)),
                  prefixIcon: const Icon(Icons.search)),
            ),
          ),
        ),
        // 通过invalidate触发重新请求
        IconButton(
          onPressed: () => ref.invalidate(accountsProvider),
          icon: const Icon(Icons.refresh)
        ),
        const SizedBox(height: 10),
        Expanded(
          child: accountsAsyncValue.when(
            loading: () => const Center(child: CircularProgressIndicator()),
            error: (error, stack) => Center(child: Text('Error: $error')),
            data: (accounts) {
              final filteredAccounts = accounts
                  .where((account) => account.actName!.toLowerCase().contains(searchString))
                  .toList();
              
              if (filteredAccounts.isEmpty) {
                return const Center(child: Text('No data available'));
              }
              
              return ListView.separated(
                shrinkWrap: true,
                padding: const EdgeInsets.all(8),
                itemCount: filteredAccounts.length,
                itemBuilder: (context, index) {
                  final account = filteredAccounts[index];
                  return Card(
                    elevation: 3,
                    child: ListTile(
                      title: Text(account.actName!),
                      subtitle: Text('${account.actMobileNo}\n${account.actPhoneNo}\n'),
                    ),
                  );
                },
                separatorBuilder: (context, index) => const Divider(),
              );
            },
          ),
        ),
      ],
    );
  }
}

关键注意事项

  • 禁止在initState()、dispose()或类构造函数中使用ref.watch(),这些生命周期方法中只能用ref.read(),因为此时组件依赖未完成初始化。
  • 使用FutureProvider能更优雅地管理异步状态,自动处理加载、错误、数据更新场景,减少手动维护代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 05:12:02