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

Riverpod中FutureProvider获数据却无法更新UI问题求助

Riverpod FutureProvider数据已返回但UI持续加载的问题排查

我是Riverpod新手,目前有两个几乎完全相同的FutureProvider,其中一个API已成功返回数据(Provider内打印result.length为预期的10),但UI始终停留在加载状态,且未执行data分支的打印逻辑。相关代码如下:

UI代码

class ProductDetailSimilarProductsWidget extends ConsumerWidget {
  const ProductDetailSimilarProductsWidget(this.product, {super.key});

  final ProductDetailModel product;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    var value = ref.watch(getSimilarProductsFutureProvider([product.Id, product.KategoriId]));
    return Container(
      height: MediaQuery.of(context).size.height * 0.5,
      width: MediaQuery.of(context).size.width,
      child: value.when(
        data: (data) {
          print("data length:${data.length}");
          return ListView.builder(
            itemCount: data.length,
            shrinkWrap: true,
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return Padding(
                padding: EdgeInsets.only(left: AppTheme.mediumPadding, right: AppTheme.mediumPadding),
                child: ProductGridContainerWidget(product: data[index]),
              );
            },
          );
        },
        error: (err, trace) {
          return Text(err.toString());
        },
        loading: () => AppTheme.spinkit,
      ),
    );
  }
}

FutureProvider代码

final getSimilarProductsFutureProvider = FutureProvider.family<List<Products>, List<dynamic>>((ref, value) async {
  var result = await ProductDetailData().getSimilarProducts(productId: value[0], categoryId: value[1]);
  print("result: ${result.length}");
  return result;
});

API请求方法代码

var service = ApiService();
var sharedPreferencesManager = SharedPreferencesManager();
await sharedPreferencesManager.init();
var langId = await sharedPreferencesManager.getLanguageId();
var countryId = await sharedPreferencesManager.getCountryId();
var result =
    await service.getRequest(subUrl: "${ApiRoutes.getSimilarProducts}?productId=$productId&langId=$langId&countryId=$countryId&categoryId=$categoryId");
List<Products> products = [];
var elements = json.decode(result.body);
await Future.forEach(elements, (element) => products.add(Products.fromMap(element as Map<String, dynamic>)));
return products;

问题排查与解决

1. 核心问题:family参数的不稳定性

你使用List<dynamic>作为family的参数类型,但List是可变对象。每次Widget重建时,[product.Id, product.KategoriId]都会创建一个新的List实例,导致FutureProvider判定参数发生变化,不断重新发起请求,陷入"加载-请求-重建-重新加载"的循环,永远无法进入data分支。

解决方法:使用不可变参数类型

  • 方案一:使用Tuple类型(需导入package:tuple/tuple.dart)

    import 'package:tuple/tuple.dart';
    
    // 修改Provider定义
    final getSimilarProductsFutureProvider = FutureProvider.family<List<Products>, Tuple2<int, int>>((ref, params) async {
      var result = await ProductDetailData().getSimilarProducts(productId: params.item1, categoryId: params.item2);
      print("result: ${result.length}");
      return result;
    });
    
    // UI中调用
    var value = ref.watch(getSimilarProductsFutureProvider(Tuple2(product.Id, product.KategoriId)));
    
  • 方案二:自定义不可变参数类

    // 定义不可变参数类,重写==和hashCode
    class SimilarProductsParams {
      final int productId;
      final int categoryId;
    
      SimilarProductsParams({required this.productId, required this.categoryId});
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is SimilarProductsParams &&
              runtimeType == other.runtimeType &&
              productId == other.productId &&
              categoryId == other.categoryId;
    
      @override
      int get hashCode => productId.hashCode ^ categoryId.hashCode;
    }
    
    // 修改Provider定义
    final getSimilarProductsFutureProvider = FutureProvider.family<List<Products>, SimilarProductsParams>((ref, params) async {
      var result = await ProductDetailData().getSimilarProducts(productId: params.productId, categoryId: params.categoryId);
      print("result: ${result.length}");
      return result;
    });
    
    // UI中调用
    var value = ref.watch(getSimilarProductsFutureProvider(SimilarProductsParams(productId: product.Id, categoryId: product.KategoriId)));
    

2. 辅助排查:捕获未处理的解析异常

虽然API返回了数据,但Products.fromMap可能在解析JSON时抛出异常,FutureProvider会内部捕获异常,但当前error分支未打印日志,无法发现问题。

可以在error分支添加日志,并在API请求方法中增加try-catch:

// UI的error分支
error: (err, trace) {
  print("加载相似商品出错: $err");
  print(trace);
  return Text(err.toString());
},

// API请求方法添加异常捕获
try {
  var elements = json.decode(result.body);
  await Future.forEach(elements, (element) => products.add(Products.fromMap(element as Map<String, dynamic>)));
} catch (e, trace) {
  print("JSON解析出错: $e");
  print(trace);
  rethrow;
}

3. 额外检查:避免父Widget频繁重建

如果父Widget因状态变化频繁重建,也会加剧重复请求问题,但解决参数稳定性后,该情况会自然改善。


内容的提问来源于stack exchange,提问作者Zahid Tekbaş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 04:12:21